REGEXP_SUBSTR
La función REGEXP_SUBSTR() extrae de una cadena la porción de texto que coincide con un patrón de expresión regular. Mientras que REGEXP_INSTR() te dice dónde está una coincidencia y REGEXP_LIKE() te dice si existe, REGEXP_SUBSTR() te devuelve el texto que coincidió. Esta función es esencial cuando necesitas extraer datos estructurados que están incrustados en campos de texto libre: números de referencia, dominios de email, códigos postales, importes o cualquier fragmento que siga un patrón reconocible.
Sintaxis
REGEXP_SUBSTR(expresion, patron [, pos [, ocurrencia [, modo]]])Los parámetros son:
| Parámetro | Descripción | Valor por defecto |
|---|---|---|
expresion | La cadena de la que extraer | (requerido) |
patron | El patrón de expresión regular | (requerido) |
pos | Posición inicial de búsqueda (1 = inicio) | 1 |
ocurrencia | Qué ocurrencia extraer (1 = primera) | 1 |
modo | Modificadores de coincidencia ('c', 'i', 'm', 'n') | depende de collation |
La función devuelve la subcadena que coincide con el patrón o NULL si no hay coincidencia o si algún argumento es NULL.
Comportamiento básico
En su forma más simple, REGEXP_SUBSTR() extrae la primera coincidencia del patrón:
SELECT REGEXP_SUBSTR('Pedido PED-2025-00142 procesado', '[A-Z]+-[0-9]+-[0-9]+') AS referencia;| referencia |
|---|
| PED-2025-00142 |
La función ha encontrado el fragmento que coincide con el patrón de letras-dígitos-dígitos y lo ha devuelto completo. Esto es mucho más cómodo que calcular posiciones con REGEXP_INSTR() y luego usar SUBSTRING() para extraer el texto.
Cuando no hay coincidencia, la función devuelve NULL:
SELECT REGEXP_SUBSTR('Sin referencias aquí', '[A-Z]+-[0-9]+') AS referencia;| referencia |
|---|
| NULL |
Un ejemplo práctico con datos de una tabla. Para extraer el primer número que aparece en la descripción de un producto:
SELECT
nombre,
descripcion,
REGEXP_SUBSTR(descripcion, '[0-9]+') AS primer_numero
FROM productos;| nombre | descripcion | primer_numero |
|---|---|---|
| Monitor LG | Pantalla de 27 pulgadas 4K | 27 |
| SSD Samsung | Disco duro sólido de 1024GB NVMe | 1024 |
| Camiseta | Algodón 100%, talla M | 100 |
| Auriculares | Cancelación activa de ruido | NULL |
Para los auriculares no se extrae ningún número porque la descripción no contiene dígitos.
Extraer la enésima ocurrencia
El parámetro ocurrencia permite extraer una coincidencia específica cuando el patrón aparece múltiples veces:
SELECT
'Dimensiones: 120x45x30 cm' AS texto,
REGEXP_SUBSTR('Dimensiones: 120x45x30 cm', '[0-9]+', 1, 1) AS largo,
REGEXP_SUBSTR('Dimensiones: 120x45x30 cm', '[0-9]+', 1, 2) AS ancho,
REGEXP_SUBSTR('Dimensiones: 120x45x30 cm', '[0-9]+', 1, 3) AS alto;| texto | largo | ancho | alto |
|---|---|---|---|
| Dimensiones: 120x45x30 cm | 120 | 45 | 30 |
Al cambiar el parámetro de ocurrencia de 1 a 3, extraemos cada dimensión por separado. El patrón [0-9]+ coincide con cada grupo de dígitos y el parámetro de ocurrencia selecciona cuál de ellos devolver.
Este enfoque es muy útil para descomponer valores compuestos almacenados en una sola columna:
SELECT
codigo_pedido,
REGEXP_SUBSTR(codigo_pedido, '[^-]+', 1, 1) AS tipo,
REGEXP_SUBSTR(codigo_pedido, '[^-]+', 1, 2) AS anio,
REGEXP_SUBSTR(codigo_pedido, '[^-]+', 1, 3) AS numero
FROM pedidos;| codigo_pedido | tipo | anio | numero |
|---|---|---|---|
| PED-2025-00142 | PED | 2025 | 00142 |
| DEV-2025-00087 | DEV | 2025 | 00087 |
| FAC-2024-12345 | FAC | 2024 | 12345 |
El patrón [^-]+ captura cualquier secuencia de caracteres que no sea un guion, lo que efectivamente divide la cadena por el delimitador -.
Parámetro pos: posición inicial
El parámetro pos permite empezar la búsqueda desde una posición específica, lo cual es útil cuando sabes que la información relevante está después de cierto punto:
-- Extraer solo el código postal (dígitos después de la coma)
SELECT
direccion,
REGEXP_SUBSTR(direccion, '[0-9]{5}', REGEXP_INSTR(direccion, ',') + 1) AS codigo_postal
FROM clientes;| direccion | codigo_postal |
|---|---|
| Calle Mayor 5, 28001 Madrid | 28001 |
| Av. Diagonal 400, 08013 Barcelona | 08013 |
| C/ Sierpes 12345, 41004 Sevilla | 41004 |
Al empezar la búsqueda después de la coma, evitamos extraer accidentalmente un número de la calle que pudiera tener cinco dígitos (como en el tercer ejemplo, donde 12345 en la calle se ignora porque la búsqueda empieza después de la coma).
Caso práctico: extraer dominios de email
Una tarea frecuente es extraer el dominio de las direcciones de email para analizarlos:
SELECT
email,
REGEXP_SUBSTR(email, '@(.+)$') AS con_arroba,
REGEXP_SUBSTR(email, '[^@]+$') AS dominio
FROM clientes
WHERE email IS NOT NULL;| con_arroba | dominio | |
|---|---|---|
| maria.garcia@gmail.com | @gmail.com | gmail.com |
| carlos.lopez@empresa.es | @empresa.es | empresa.es |
| ana@mi.universidad.edu | @mi.universidad.edu | mi.universidad.edu |
El patrón [^@]+$ captura todos los caracteres que no son @ al final de la cadena, lo que da el dominio completo. Puedes usar esto para generar un informe de distribución de dominios:
SELECT
REGEXP_SUBSTR(email, '[^@]+$') AS dominio,
COUNT(*) AS cantidad
FROM clientes
WHERE email IS NOT NULL
GROUP BY dominio
ORDER BY cantidad DESC
LIMIT 10;| dominio | cantidad |
|---|---|
| gmail.com | 95 |
| outlook.com | 42 |
| hotmail.com | 28 |
| yahoo.es | 19 |
| empresa.es | 15 |
| icloud.com | 12 |
| protonmail.com | 8 |
| live.com | 7 |
| yahoo.com | 6 |
| educacion.es | 5 |
Caso práctico: extraer importes de texto libre
En campos de notas o descripciones, a menudo aparecen importes que necesitas extraer para cálculos:
SELECT
notas,
REGEXP_SUBSTR(notas, '[0-9]+[.,][0-9]{2}') AS importe_texto,
CAST(REGEXP_REPLACE(
REGEXP_SUBSTR(notas, '[0-9]+[.,][0-9]{2}'),
',', '.'
) AS DECIMAL(10,2)) AS importe_numerico
FROM pedidos
WHERE REGEXP_SUBSTR(notas, '[0-9]+[.,][0-9]{2}') IS NOT NULL;| notas | importe_texto | importe_numerico |
|---|---|---|
| Reembolso parcial de 45,99 EUR | 45,99 | 45.99 |
| Descuento aplicado: 120.50 | 120.50 | 120.50 |
| Cargo adicional de 8,00 por envío urgente | 8,00 | 8.00 |
La combinación de REGEXP_SUBSTR() para extraer, REGEXP_REPLACE() para normalizar la coma decimal y CAST() para convertir a número permite procesar importes incrustados en texto de forma automática.
Caso práctico: extraer datos de campos semiestructurados
Cuando trabajas con campos que contienen pares clave-valor o datos semiestructurados, REGEXP_SUBSTR() permite extraer valores específicos:
SELECT
especificaciones,
REGEXP_SUBSTR(especificaciones, 'Color: ([a-zA-ZáéíóúÁÉÍÓÚñÑ]+)') AS color_completo,
REGEXP_SUBSTR(especificaciones, 'Peso: ([0-9.]+\\s*(kg|g))') AS peso_completo
FROM productos;| especificaciones | color_completo | peso_completo |
|---|---|---|
| Color: Negro, Peso: 1.2 kg, Material: Aluminio | Color: Negro | Peso: 1.2 kg |
| Color: Blanco, Peso: 350 g, Tamaño: M | Color: Blanco | Peso: 350 g |
| Material: Plástico, Peso: 0.5 kg | NULL | Peso: 0.5 kg |
El tercer producto no tiene color en sus especificaciones, por lo que REGEXP_SUBSTR() devuelve NULL para ese campo. Esto permite construir consultas que extraen información estructurada de campos de texto de forma robusta, sin fallar cuando la información esperada no está presente.
Extraer múltiples coincidencias en columnas separadas
Aunque REGEXP_SUBSTR() solo devuelve una coincidencia por llamada, puedes usar el parámetro de ocurrencia para extraer múltiples valores en columnas separadas:
SELECT
etiquetas,
REGEXP_SUBSTR(etiquetas, '[^,]+', 1, 1) AS etiqueta_1,
REGEXP_SUBSTR(etiquetas, '[^,]+', 1, 2) AS etiqueta_2,
REGEXP_SUBSTR(etiquetas, '[^,]+', 1, 3) AS etiqueta_3
FROM productos;| etiquetas | etiqueta_1 | etiqueta_2 | etiqueta_3 |
|---|---|---|---|
| electrónica,smartphone,premium | electrónica | smartphone | premium |
| hogar,cocina | hogar | cocina | NULL |
| deportes,running,fitness,outdoor | deportes | running | fitness |
| oferta | oferta | NULL | NULL |
Este enfoque es una alternativa ligera a dividir la cadena en filas cuando solo necesitas un número fijo de valores. Para el producto con cuatro etiquetas, la cuarta (outdoor) no se extrae porque solo pedimos tres columnas, pero podríamos añadir una cuarta llamada con ocurrencia 4.
Manejo de NULL
REGEXP_SUBSTR() devuelve NULL en tres situaciones: cuando la expresión es NULL, cuando el patrón es NULL o cuando no hay coincidencia. Es importante distinguir entre "campo nulo" y "patrón no encontrado":
SELECT
nombre,
descripcion,
REGEXP_SUBSTR(descripcion, '[0-9]+\\s*(GB|TB)') AS almacenamiento,
CASE
WHEN descripcion IS NULL THEN 'Sin descripción'
WHEN REGEXP_SUBSTR(descripcion, '[0-9]+\\s*(GB|TB)') IS NULL THEN 'No especificado'
ELSE REGEXP_SUBSTR(descripcion, '[0-9]+\\s*(GB|TB)')
END AS almacenamiento_detallado
FROM productos;| nombre | descripcion | almacenamiento | almacenamiento_detallado |
|---|---|---|---|
| iPhone 15 Pro | Almacenamiento de 256GB | 256GB | 256GB |
| MacBook Air | SSD de 1TB ultrarrápido | 1TB | 1TB |
| Camiseta básica | Algodón 100% | NULL | No especificado |
| Producto nuevo | NULL | NULL | Sin descripción |
El CASE permite dar un mensaje diferente según si el campo era nulo o si simplemente no contenía el patrón buscado.
Combinación con otras funciones
REGEXP_SUBSTR() se integra perfectamente con funciones numéricas y de cadena para extraer y transformar datos en un solo paso:
-- Extraer y sumar todos los importes mencionados en las notas
SELECT
id,
notas,
CAST(REGEXP_SUBSTR(notas, '[0-9]+\\.?[0-9]*', 1, 1) AS DECIMAL(10,2)) AS importe_1,
CAST(REGEXP_SUBSTR(notas, '[0-9]+\\.?[0-9]*', 1, 2) AS DECIMAL(10,2)) AS importe_2,
COALESCE(CAST(REGEXP_SUBSTR(notas, '[0-9]+\\.?[0-9]*', 1, 1) AS DECIMAL(10,2)), 0)
+ COALESCE(CAST(REGEXP_SUBSTR(notas, '[0-9]+\\.?[0-9]*', 1, 2) AS DECIMAL(10,2)), 0)
AS suma
FROM pedidos
WHERE REGEXP_LIKE(notas, '[0-9]+\\.?[0-9]*');| id | notas | importe_1 | importe_2 | suma |
|---|---|---|---|---|
| 101 | Producto 89.99 + envío 5.50 | 89.99 | 5.50 | 95.49 |
| 102 | Descuento de 15.00 aplicado | 15.00 | NULL | 15.00 |
También puedes combinar REGEXP_SUBSTR() con CONCAT() para construir cadenas nuevas a partir de las partes extraídas:
SELECT
telefono,
CONCAT(
'(',
REGEXP_SUBSTR(REGEXP_REPLACE(telefono, '[^0-9]', ''), '[0-9]{3}', 1, 1),
') ',
REGEXP_SUBSTR(REGEXP_REPLACE(telefono, '[^0-9]', ''), '[0-9]{3}', 1, 2),
'-',
REGEXP_SUBSTR(REGEXP_REPLACE(telefono, '[^0-9]', ''), '[0-9]{3}', 1, 3)
) AS telefono_formateado
FROM clientes
WHERE telefono IS NOT NULL;| telefono | telefono_formateado |
|---|---|
| 612345678 | (612) 345-678 |
| +34678901234 | (346) 789-012 |
| 912-345-678 | (912) 345-678 |
Con esto completamos la sección de expresiones regulares. En la siguiente sección veremos las funciones JSON de MySQL.
Escrito por Eduardo Lázaro
