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ámetroDescripciónValor por defecto
expresionLa cadena original(requerido)
patronEl patrón de expresión regular a buscar(requerido)
reemplazoEl texto de reemplazo (admite backreferences \1, \2, etc.)(requerido)
posPosición inicial de búsqueda1
ocurrenciaQué ocurrencia reemplazar (0 = todas)0
modoModificadores 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_digitogrupo_a_grupo
Precio: ####.## EURPrecio: #.# 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;
originallimpio
María García #123María García
Carlos (Admin) LópezCarlos Admin López
Ana MartínezAna Martínez

Para normalizar espacios múltiples en uno solo:

SELECT
    direccion AS original,
    REGEXP_REPLACE(TRIM(direccion), '\\s{2,}', ' ') AS normalizada
FROM clientes;
originalnormalizada
Calle Gran Vía 42Calle Gran Vía 42
Av. Diagonal 400Av. Diagonal 400
Plaza Mayor 1Plaza 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_htmlsin_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;
originalsolo_digitosestandarizado
612-345-678612345678+34 612 345 678
+34 678 901 234+34678901234+34 678 901 234
(91) 234.56.78912345678+34 912 345 678
0034-6123456780034612345678+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;
emailemail_anonimo
maria.garcia@gmail.comm***@gmail.com
carlos.lopez@empresa.esc***@empresa.es
ana.martinez@outlook.coma***@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;
telefonotelefono_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;
originaltodasprimerasegunda
rojo, verde, azul, rojo, amarilloROJO, verde, azul, ROJO, amarilloROJO, verde, azul, rojo, amarillorojo, 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;
nombrenombre_busqueda
Cámara Réflexcamara reflex
Lámpara LEDlampara LED
Teléfono inalámbricotelefono 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;
nombretelefonosolo_digitosseguro
María García612-345-678612345678612345678
Carlos López+34 678 9013467890134678901
Pedro RuizNULLNULLSin 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;
nombreslug
iPhone 15 Pro (128GB)iphone-15-pro-128gb
Samsung Galaxy S24samsung-galaxy-s24
Cargador USB-C 65Wcargador-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