ACTUALIZACIÓN 2: Un conjunto útil de funciones regex que incluye REGEXP_REPLACE ahora se ha proporcionado en MySQL 8.0. Esto hace que la lectura sea innecesaria a menos que esté limitado a usar una versión anterior.
ACTUALIZACIÓN 1: Ahora he convertido esto en una publicación de blog: http://stevettt.blogspot.co.uk/2018/02/a-mysql-regular-expression-replace.html
Lo siguiente se expande sobre la función proporcionada por Rasika Godawatte, pero rastrea todas las subcadenas necesarias en lugar de solo probar caracteres individuales:
-- ------------------------------------------------------------------------------------
-- USAGE
-- ------------------------------------------------------------------------------------
-- SELECT reg_replace(<subject>,
-- <pattern>,
-- <replacement>,
-- <greedy>,
-- <minMatchLen>,
-- <maxMatchLen>);
-- where:
-- <subject> is the string to look in for doing the replacements
-- <pattern> is the regular expression to match against
-- <replacement> is the replacement string
-- <greedy> is TRUE for greedy matching or FALSE for non-greedy matching
-- <minMatchLen> specifies the minimum match length
-- <maxMatchLen> specifies the maximum match length
-- (minMatchLen and maxMatchLen are used to improve efficiency but are
-- optional and can be set to 0 or NULL if not known/required)
-- Example:
-- SELECT reg_replace(txt, '^[Tt][^ ]* ', 'a', TRUE, 2, 0) FROM tbl;
DROP FUNCTION IF EXISTS reg_replace;
DELIMITER //
CREATE FUNCTION reg_replace(subject VARCHAR(21845), pattern VARCHAR(21845),
replacement VARCHAR(21845), greedy BOOLEAN, minMatchLen INT, maxMatchLen INT)
RETURNS VARCHAR(21845) DETERMINISTIC BEGIN
DECLARE result, subStr, usePattern VARCHAR(21845);
DECLARE startPos, prevStartPos, startInc, len, lenInc INT;
IF subject REGEXP pattern THEN
SET result = '';
-- Sanitize input parameter values
SET minMatchLen = IF(minMatchLen < 1, 1, minMatchLen);
SET maxMatchLen = IF(maxMatchLen < 1 OR maxMatchLen > CHAR_LENGTH(subject),
CHAR_LENGTH(subject), maxMatchLen);
-- Set the pattern to use to match an entire string rather than part of a string
SET usePattern = IF (LEFT(pattern, 1) = '^', pattern, CONCAT('^', pattern));
SET usePattern = IF (RIGHT(pattern, 1) = '$', usePattern, CONCAT(usePattern, '$'));
-- Set start position to 1 if pattern starts with ^ or doesn't end with $.
IF LEFT(pattern, 1) = '^' OR RIGHT(pattern, 1) <> '$' THEN
SET startPos = 1, startInc = 1;
-- Otherwise (i.e. pattern ends with $ but doesn't start with ^): Set start pos
-- to the min or max match length from the end (depending on "greedy" flag).
ELSEIF greedy THEN
SET startPos = CHAR_LENGTH(subject) - maxMatchLen + 1, startInc = 1;
ELSE
SET startPos = CHAR_LENGTH(subject) - minMatchLen + 1, startInc = -1;
END IF;
WHILE startPos >= 1 AND startPos <= CHAR_LENGTH(subject)
AND startPos + minMatchLen - 1 <= CHAR_LENGTH(subject)
AND !(LEFT(pattern, 1) = '^' AND startPos <> 1)
AND !(RIGHT(pattern, 1) = '$'
AND startPos + maxMatchLen - 1 < CHAR_LENGTH(subject)) DO
-- Set start length to maximum if matching greedily or pattern ends with $.
-- Otherwise set starting length to the minimum match length.
IF greedy OR RIGHT(pattern, 1) = '$' THEN
SET len = LEAST(CHAR_LENGTH(subject) - startPos + 1, maxMatchLen), lenInc = -1;
ELSE
SET len = minMatchLen, lenInc = 1;
END IF;
SET prevStartPos = startPos;
lenLoop: WHILE len >= 1 AND len <= maxMatchLen
AND startPos + len - 1 <= CHAR_LENGTH(subject)
AND !(RIGHT(pattern, 1) = '$'
AND startPos + len - 1 <> CHAR_LENGTH(subject)) DO
SET subStr = SUBSTRING(subject, startPos, len);
IF subStr REGEXP usePattern THEN
SET result = IF(startInc = 1,
CONCAT(result, replacement), CONCAT(replacement, result));
SET startPos = startPos + startInc * len;
LEAVE lenLoop;
END IF;
SET len = len + lenInc;
END WHILE;
IF (startPos = prevStartPos) THEN
SET result = IF(startInc = 1, CONCAT(result, SUBSTRING(subject, startPos, 1)),
CONCAT(SUBSTRING(subject, startPos, 1), result));
SET startPos = startPos + startInc;
END IF;
END WHILE;
IF startInc = 1 AND startPos <= CHAR_LENGTH(subject) THEN
SET result = CONCAT(result, RIGHT(subject, CHAR_LENGTH(subject) + 1 - startPos));
ELSEIF startInc = -1 AND startPos >= 1 THEN
SET result = CONCAT(LEFT(subject, startPos), result);
END IF;
ELSE
SET result = subject;
END IF;
RETURN result;
END//
DELIMITER ;
Manifestación
Demo de Rextester
Limitaciones
- Por supuesto, este método llevará un tiempo cuando la cadena de asunto es grande. Actualización: ahora se han agregado parámetros de longitud de coincidencia mínima y máxima para mejorar la eficiencia cuando se conocen (cero = desconocido / ilimitado).
- Es no permitir sustitución de referencias hacia atrás (por ejemplo
\1
, \2
etc.) para sustituir a los grupos de captura. Si se necesita esta funcionalidad, consulte esta respuesta que intenta proporcionar una solución alternativa actualizando la función para permitir una búsqueda y reemplazo secundario dentro de cada coincidencia encontrada (a expensas de una mayor complejidad).
- Si
^
y / o $
se usa en el patrón, deben estar al principio y al final respectivamente, por ejemplo, patrones como los que (^start|end$)
no son compatibles.
- Hay un indicador "codicioso" para especificar si la coincidencia general debe ser codiciosa o no codiciosa.
a.*?b.*
No se admite la combinación de coincidencia codiciosa y perezosa dentro de una sola expresión regular (p . Ej. ).
Ejemplos de uso
La función se ha utilizado para responder las siguientes preguntas de StackOverflow:
regexp_split
(función + procedimiento) ®exp_replace
, que se implementan con elREGEXP
operador. Para búsquedas simples, hará el truco. Puede encontrarlo aquí , así que esta es la forma con el código almacenado de MySQL, sin UDF. Si encuentra algunos errores, que no están cubiertos por limitaciones conocidas, no dude en abrir el problema.