REGEXP_REPLACE
La función REGEXP_REPLACE() busca un patrón de expresión regular en una cadena y reemplaza las coincidencias por un texto nuevo. Es la herramienta más potente de MySQL para transformar y limpiar datos textuales. Mientras que la función REPLACE() solo trabaja con cadenas literales, REGEXP_REPLACE() permite reemplazar patrones: dígitos, espacios múltiples, formatos de fecha, caracteres especiales o cualquier combinación que puedas expresar con una expresión regular.
Esta función es especialmente valiosa en tareas de limpieza de datos, normalización de formatos, anonimización de información sensible y preparación de datos para migración entre sistemas.
Sintaxis
REGEXP_REPLACE(expresion, patron, reemplazo [, pos [, ocurrencia [, modo]]])Los parámetros son:
| Parámetro | Descripción | Valor por defecto |
|---|---|---|
expresion | La cadena original | (requerido) |
patron | El patrón de expresión regular a buscar | (requerido) |
reemplazo | El texto de reemplazo (admite backreferences \1, \2, etc.) | (requerido) |
pos | Posición inicial de búsqueda | 1 |
ocurrencia | Qué ocurrencia reemplazar (0 = todas) | 0 |
modo | Modificadores de coincidencia ('c', 'i', 'm', 'n') | depende de collation |
La función devuelve la cadena modificada o NULL si algún argumento es NULL. Por defecto reemplaza todas las ocurrencias del patrón. Si quieres reemplazar solo una ocurrencia específica, usa el parámetro ocurrencia.
Comportamiento básico
En su forma más simple, REGEXP_REPLACE() busca un patrón y lo reemplaza por una cadena fija:
SELECT REGEXP_REPLACE('Mi teléfono es 612-345-678', '[0-9]', 'X') AS resultado;| resultado |
|---|
| Mi teléfono es XXX-XXX-XXX |
Cada dígito individual ha sido reemplazado por una X. Si quisieras reemplazar cada grupo de dígitos por una sola X, usarías el cuantificador +:
SELECT REGEXP_REPLACE('Mi teléfono es 612-345-678', '[0-9]+', 'XXX') AS resultado;| resultado |
|---|
| Mi teléfono es XXX-XXX-XXX |
Ahora cada grupo de dígitos consecutivos se reemplaza como una unidad. El resultado visual es el mismo en este caso, pero la diferencia es importante: en el primer ejemplo se hacen 9 reemplazos individuales, en el segundo solo 3.
Veamos un ejemplo más claro de la diferencia:
SELECT
REGEXP_REPLACE('Precio: 1299.99 EUR', '[0-9]', '#') AS digito_a_digito,
REGEXP_REPLACE('Precio: 1299.99 EUR', '[0-9]+', '#') AS grupo_a_grupo;| digito_a_digito | grupo_a_grupo |
|---|---|
| Precio: ####.## EUR | Precio: #.# EUR |
Backreferences: reutilizar partes de la coincidencia
Una de las características más potentes de REGEXP_REPLACE() es la capacidad de usar backreferences en el texto de reemplazo. Los grupos capturados con paréntesis en el patrón se pueden referenciar como \\1, \\2, etc. en el reemplazo:
-- Cambiar formato de fecha DD/MM/AAAA a AAAA-MM-DD
SELECT REGEXP_REPLACE(
'15/03/2025',
'([0-9]{2})/([0-9]{2})/([0-9]{4})',
'\\3-\\2-\\1'
) AS fecha_iso;| fecha_iso |
|---|
| 2025-03-15 |
El patrón captura tres grupos: día (\\1), mes (\\2) y año (\\3). En el reemplazo, los reordena al formato ISO. Esto es invaluable para normalizar fechas que provienen de distintas fuentes.
Otro ejemplo práctico: reformatear números de teléfono:
SELECT REGEXP_REPLACE(
'612345678',
'([0-9]{3})([0-9]{3})([0-9]{3})',
'\\1 \\2 \\3'
) AS telefono_formateado;| telefono_formateado |
|---|
| 612 345 678 |
Los paréntesis capturan tres grupos de tres dígitos y el reemplazo los separa con espacios.
Caso práctico: limpieza de datos
La limpieza de datos es el escenario donde REGEXP_REPLACE() brilla con más fuerza. Veamos varios problemas habituales y cómo resolverlos.
Para eliminar caracteres especiales de nombres, dejando solo letras, espacios y caracteres acentuados:
SELECT
nombre AS original,
REGEXP_REPLACE(nombre, '[^a-zA-ZáéíóúÁÉÍÓÚñÑüÜ ]', '') AS limpio
FROM clientes;| original | limpio |
|---|---|
| María García #123 | María García |
| Carlos (Admin) López | Carlos Admin López |
| Ana Martínez | Ana Martínez |
Para normalizar espacios múltiples en uno solo:
SELECT
direccion AS original,
REGEXP_REPLACE(TRIM(direccion), '\\s{2,}', ' ') AS normalizada
FROM clientes;| original | normalizada |
|---|---|
| Calle Gran Vía 42 | Calle Gran Vía 42 |
| Av. Diagonal 400 | Av. Diagonal 400 |
| Plaza Mayor 1 | Plaza Mayor 1 |
La combinación de TRIM() para eliminar espacios al inicio y final con REGEXP_REPLACE() para reducir espacios internos múltiples produce direcciones limpias y uniformes.
Para eliminar etiquetas HTML de un campo de texto:
SELECT
descripcion AS con_html,
REGEXP_REPLACE(descripcion, '<[^>]+>', '') AS sin_html
FROM productos;| con_html | sin_html |
|---|---|
Portátil de <b>alta gama</b> | Portátil de alta gama |
Color: <span style="color:red">rojo</span> | Color: rojo |
Incluye <a href="/garantia">garantía</a> | Incluye garantía |
Caso práctico: normalizar teléfonos
Un problema muy común es que los teléfonos se almacenan con distintos formatos. Con REGEXP_REPLACE() puedes estandarizarlos:
SELECT
telefono AS original,
REGEXP_REPLACE(telefono, '[^0-9+]', '') AS solo_digitos,
CONCAT('+34 ',
REGEXP_REPLACE(
REGEXP_REPLACE(telefono, '[^0-9]', ''),
'(34)?([0-9]{3})([0-9]{3})([0-9]{3})',
'\\2 \\3 \\4'
)
) AS estandarizado
FROM clientes
WHERE telefono IS NOT NULL;| original | solo_digitos | estandarizado |
|---|---|---|
| 612-345-678 | 612345678 | +34 612 345 678 |
| +34 678 901 234 | +34678901234 | +34 678 901 234 |
| (91) 234.56.78 | 912345678 | +34 912 345 678 |
| 0034-612345678 | 0034612345678 | +34 612 345 678 |
El primer paso elimina todo lo que no sea dígito con [^0-9]. El segundo paso captura los grupos del número y los reformatea con el patrón deseado. El prefijo opcional 34 se maneja como grupo opcional para evitar duplicarlo.
Caso práctico: anonimizar datos sensibles
Cuando necesitas compartir datos para pruebas o análisis pero debes proteger la información personal, REGEXP_REPLACE() permite anonimizar de forma selectiva:
SELECT
email,
REGEXP_REPLACE(email, '(.).+(@)', '\\1***\\2') AS email_anonimo
FROM clientes;| email_anonimo | |
|---|---|
| maria.garcia@gmail.com | m***@gmail.com |
| carlos.lopez@empresa.es | c***@empresa.es |
| ana.martinez@outlook.com | a***@outlook.com |
El patrón captura el primer carácter antes de la arroba y la arroba misma, reemplazando todo lo intermedio con asteriscos.
Para anonimizar teléfonos mostrando solo los últimos 3 dígitos:
SELECT
telefono,
REGEXP_REPLACE(telefono, '[0-9](?=[0-9]{3})', '*') AS telefono_parcial
FROM clientes
WHERE telefono IS NOT NULL;| telefono | telefono_parcial |
|---|---|
| 612345678 | ******678 |
| +34678901234 | +**********234 |
Parámetro ocurrencia: reemplazo selectivo
El parámetro ocurrencia permite reemplazar solo una coincidencia específica en lugar de todas. Con 0 (valor por defecto) se reemplazan todas; con un número positivo, solo se reemplaza esa ocurrencia:
SELECT
'rojo, verde, azul, rojo, amarillo' AS original,
REGEXP_REPLACE('rojo, verde, azul, rojo, amarillo', 'rojo', 'ROJO', 1, 0) AS todas,
REGEXP_REPLACE('rojo, verde, azul, rojo, amarillo', 'rojo', 'ROJO', 1, 1) AS primera,
REGEXP_REPLACE('rojo, verde, azul, rojo, amarillo', 'rojo', 'ROJO', 1, 2) AS segunda;| original | todas | primera | segunda |
|---|---|---|---|
| rojo, verde, azul, rojo, amarillo | ROJO, verde, azul, ROJO, amarillo | ROJO, verde, azul, rojo, amarillo | rojo, verde, azul, ROJO, amarillo |
Esto es útil cuando necesitas modificar solo la primera aparición de un patrón sin afectar al resto:
-- Capitalizar solo la primera palabra de cada descripción
SELECT
descripcion,
REGEXP_REPLACE(descripcion, '^([a-záéíóúñ])', UPPER('\\1'), 1, 1, 'c') AS capitalizada
FROM productos;Caso práctico: normalizar datos para búsqueda
Cuando implementas una búsqueda interna, a menudo necesitas normalizar tanto el texto almacenado como el término de búsqueda:
-- Crear una versión normalizada del nombre para búsqueda
SELECT
nombre,
LOWER(
REGEXP_REPLACE(
REGEXP_REPLACE(nombre, '[áàäâ]', 'a'),
'[éèëê]', 'e'
)
) AS nombre_busqueda
FROM productos;| nombre | nombre_busqueda |
|---|---|
| Cámara Réflex | camara reflex |
| Lámpara LED | lampara LED |
| Teléfono inalámbrico | telefono inalambrico |
Para una normalización completa encadenarías múltiples reemplazos que cubran todas las vocales acentuadas. En la práctica, esta transformación se suele almacenar en una columna adicional indexada para que las búsquedas sean rápidas.
Manejo de NULL
REGEXP_REPLACE() devuelve NULL si cualquiera de los tres argumentos obligatorios es NULL:
SELECT
nombre,
telefono,
REGEXP_REPLACE(telefono, '[^0-9]', '') AS solo_digitos,
COALESCE(REGEXP_REPLACE(telefono, '[^0-9]', ''), 'Sin teléfono') AS seguro
FROM clientes;| nombre | telefono | solo_digitos | seguro |
|---|---|---|---|
| María García | 612-345-678 | 612345678 | 612345678 |
| Carlos López | +34 678 901 | 34678901 | 34678901 |
| Pedro Ruiz | NULL | NULL | Sin teléfono |
Usa COALESCE o IFNULL para proporcionar un valor por defecto cuando el campo original es nulo.
Combinación con otras funciones
REGEXP_REPLACE() se combina muy bien con otras funciones de cadena para crear transformaciones complejas:
-- Generar un slug URL-friendly a partir de un nombre de producto
SELECT
nombre,
LOWER(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(nombre, '[^a-zA-Z0-9áéíóúñ ]', ''),
'\\s+', '-'
),
'-{2,}', '-'
)
) AS slug
FROM productos;| nombre | slug |
|---|---|
| iPhone 15 Pro (128GB) | iphone-15-pro-128gb |
| Samsung Galaxy S24 | samsung-galaxy-s24 |
| Cargador USB-C 65W | cargador-usb-c-65w |
La transformación se aplica en tres pasos: primero elimina caracteres especiales, luego convierte espacios en guiones y finalmente elimina guiones consecutivos.
También puedes usar REGEXP_REPLACE() en sentencias UPDATE para corregir datos masivamente:
-- Normalizar todos los teléfonos de la base de datos
UPDATE clientes
SET telefono = CONCAT('+34',
REGEXP_REPLACE(
REGEXP_REPLACE(telefono, '[^0-9]', ''),
'^(34)?', ''
)
)
WHERE telefono IS NOT NULL
AND telefono NOT REGEXP '^\\+34[0-9]{9}$';Esta sentencia actualiza todos los teléfonos que no están en formato estándar, eliminando cualquier formato previo y aplicando el formato +34XXXXXXXXX de forma uniforme. Es una operación de limpieza masiva que sería imposible con la función REPLACE() normal porque los formatos de origen varían.
En el siguiente artículo veremos REGEXP_SUBSTR para extraer texto que coincida con un patrón.
Escrito por Eduardo Lázaro
