JSON_EXTRACT
La función JSON_EXTRACT es probablemente la función JSON que más usarás en MySQL. Permite extraer uno o varios valores de un documento JSON usando expresiones JSON Path. Cada vez que necesites leer un dato almacenado dentro de una columna JSON, ya sea para mostrarlo en el resultado, filtrarlo en un WHERE o usarlo en un cálculo, JSON_EXTRACT es la herramienta fundamental. MySQL además ofrece dos operadores abreviados (-> y ->>) que simplifican la sintaxis en los casos más comunes.
Sintaxis
JSON_EXTRACT(documento_json, ruta [, ruta2, ruta3, ...])El primer argumento es el documento JSON (una columna de tipo JSON o una cadena JSON válida). Los siguientes argumentos son una o más expresiones JSON Path que indican qué valores extraer. Si proporcionas una sola ruta, el resultado es el valor encontrado. Si proporcionas varias rutas, el resultado es un array JSON con todos los valores encontrados.
Comportamiento básico
Supongamos una tabla de productos con atributos almacenados en JSON:
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(200) NOT NULL,
detalles JSON
);
INSERT INTO productos (nombre, detalles) VALUES
('MacBook Air M3', '{"marca": "Apple", "precio": 1399.00, "color": "Medianoche", "specs": {"ram": "16GB", "ssd": "512GB"}}'),
('Galaxy S24 Ultra', '{"marca": "Samsung", "precio": 1459.99, "color": "Titanio", "specs": {"ram": "12GB", "pantalla": "6.8\""}}'),
('ThinkPad X1', '{"marca": "Lenovo", "precio": 1549.00, "color": "Negro", "specs": {"ram": "32GB", "ssd": "1TB"}}');Extraer un valor simple:
SELECT nombre, JSON_EXTRACT(detalles, '$.marca') AS marca
FROM productos;| nombre | marca |
|---|---|
| MacBook Air M3 | "Apple" |
| Galaxy S24 Ultra | "Samsung" |
| ThinkPad X1 | "Lenovo" |
Observa que el resultado viene entrecomillado: "Apple" en lugar de Apple. Esto se debe a que JSON_EXTRACT devuelve un valor de tipo JSON, y las cadenas JSON llevan comillas. Esto es importante entenderlo para evitar problemas en comparaciones.
El operador -> (alias de JSON_EXTRACT)
MySQL ofrece el operador -> como una forma abreviada de JSON_EXTRACT. Las dos expresiones siguientes son exactamente equivalentes:
-- Forma larga
SELECT JSON_EXTRACT(detalles, '$.marca') FROM productos;
-- Forma abreviada con ->
SELECT detalles->'$.marca' FROM productos;Ambas devuelven el mismo resultado: el valor JSON entrecomillado. El operador -> solo funciona con columnas de tabla, no con cadenas literales.
El operador ->> (extracción sin comillas)
El operador ->> combina JSON_EXTRACT con JSON_UNQUOTE. Extrae el valor y le quita las comillas JSON, devolviendo una cadena SQL normal:
SELECT nombre,
detalles->'$.marca' AS con_comillas,
detalles->>'$.marca' AS sin_comillas
FROM productos;| nombre | con_comillas | sin_comillas |
|---|---|---|
| MacBook Air M3 | "Apple" | Apple |
| Galaxy S24 Ultra | "Samsung" | Samsung |
| ThinkPad X1 | "Lenovo" | Lenovo |
La diferencia es crucial cuando necesitas comparar valores. Si usas -> en un WHERE, debes comparar con la cadena JSON entrecomillada:
-- Con -> necesitas comillas JSON dentro de la cadena SQL
SELECT nombre FROM productos WHERE detalles->'$.marca' = '"Apple"';
-- Con ->> comparas directamente con la cadena SQL
SELECT nombre FROM productos WHERE detalles->>'$.marca' = 'Apple';La segunda forma es mucho más intuitiva y menos propensa a errores. Como regla general, usa ->> cuando necesites comparar o mostrar valores de texto, y -> cuando necesites preservar el tipo JSON del resultado (por ejemplo, para pasar a otra función JSON).
Extraer valores anidados
Para acceder a valores dentro de objetos anidados, encadena los accesos con puntos en la ruta:
SELECT nombre,
detalles->>'$.specs.ram' AS ram,
detalles->>'$.specs.ssd' AS ssd
FROM productos;| nombre | ram | ssd |
|---|---|---|
| MacBook Air M3 | 16GB | 512GB |
| Galaxy S24 Ultra | 12GB | NULL |
| ThinkPad X1 | 32GB | 1TB |
El Galaxy S24 Ultra no tiene la clave ssd en sus especificaciones, por lo que la extracción devuelve NULL.
Extraer múltiples rutas a la vez
Cuando proporcionas varias rutas, JSON_EXTRACT devuelve un array JSON con todos los valores encontrados:
SELECT nombre,
JSON_EXTRACT(detalles, '$.marca', '$.color', '$.precio') AS resumen
FROM productos;| nombre | resumen |
|---|---|
| MacBook Air M3 | ["Apple", "Medianoche", 1399.00] |
| Galaxy S24 Ultra | ["Samsung", "Titanio", 1459.99] |
| ThinkPad X1 | ["Lenovo", "Negro", 1549.00] |
Esto es útil cuando necesitas extraer varios valores en una sola operación, aunque en la práctica es más habitual extraer cada valor en su propia columna.
Caso práctico: filtrar y ordenar por valores JSON
Una de las aplicaciones más frecuentes es usar valores extraídos del JSON para filtrar y ordenar resultados:
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
cliente VARCHAR(100),
datos JSON
);
INSERT INTO pedidos (cliente, datos) VALUES
('Laura M.', '{"total": 259.98, "items": 3, "envio": {"tipo": "express", "coste": 9.99}, "cupon": "VERANO10"}'),
('Carlos F.', '{"total": 89.99, "items": 1, "envio": {"tipo": "estandar", "coste": 4.99}, "cupon": null}'),
('Elena R.', '{"total": 450.00, "items": 5, "envio": {"tipo": "express", "coste": 0}, "cupon": "PREMIUM"}'),
('Miguel T.', '{"total": 134.97, "items": 2, "envio": {"tipo": "estandar", "coste": 4.99}, "cupon": null}');Filtrar pedidos con envío express y total superior a 200:
SELECT cliente,
datos->>'$.total' AS total,
datos->>'$.envio.tipo' AS tipo_envio,
datos->>'$.cupon' AS cupon
FROM pedidos
WHERE datos->>'$.envio.tipo' = 'express'
AND CAST(datos->>'$.total' AS DECIMAL(10,2)) > 200;| cliente | total | tipo_envio | cupon |
|---|---|---|---|
| Laura M. | 259.98 | express | VERANO10 |
| Elena R. | 450.00 | express | PREMIUM |
Observa el uso de CAST para la comparación numérica. El operador ->> devuelve cadenas, así que para comparaciones numéricas debes convertir explícitamente el tipo.
Caso práctico: extraer elementos de arrays
JSON_EXTRACT también funciona con arrays JSON:
CREATE TABLE recetas (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(200),
datos JSON
);
INSERT INTO recetas (nombre, datos) VALUES
('Paella valenciana', '{"tiempo_minutos": 45, "porciones": 4, "ingredientes": ["arroz", "judía verde", "garrofón", "pollo", "conejo", "azafrán", "tomate", "aceite"]}'),
('Tortilla española', '{"tiempo_minutos": 25, "porciones": 6, "ingredientes": ["huevos", "patatas", "cebolla", "aceite", "sal"]}');SELECT nombre,
datos->>'$.ingredientes[0]' AS ingrediente_principal,
datos->>'$.ingredientes[1]' AS segundo_ingrediente,
datos->'$.ingredientes' AS todos
FROM recetas;| nombre | ingrediente_principal | segundo_ingrediente | todos |
|---|---|---|---|
| Paella valenciana | arroz | judía verde | ["arroz", "judía verde", "garrofón", "pollo", "conejo", "azafrán", "tomate", "aceite"] |
| Tortilla española | huevos | patatas | ["huevos", "patatas", "cebolla", "aceite", "sal"] |
Para la columna todos usamos -> (sin las comillas extra) porque queremos el array JSON completo, no una cadena.
Manejo de NULL
JSON_EXTRACT devuelve NULL en dos situaciones: cuando la columna JSON es NULL y cuando la ruta no existe en el documento:
INSERT INTO productos (nombre, detalles) VALUES
('Producto sin detalles', NULL),
('Producto parcial', '{"marca": "Genérico"}');SELECT nombre,
detalles->>'$.marca' AS marca,
detalles->>'$.color' AS color
FROM productos
WHERE id > 3;| nombre | marca | color |
|---|---|---|
| Producto sin detalles | NULL | NULL |
| Producto parcial | Genérico | NULL |
En el primer caso, ambas extracciones devuelven NULL porque la columna entera es NULL. En el segundo, marca devuelve un valor pero color devuelve NULL porque esa clave no existe en el documento. Este comportamiento seguro permite trabajar con documentos de estructura variable sin riesgo de errores.
Combinación con otras funciones
Puedes usar JSON_EXTRACT dentro de expresiones y combinarlo con funciones SQL regulares:
SELECT nombre,
UPPER(detalles->>'$.marca') AS marca_mayus,
CONCAT(detalles->>'$.specs.ram', ' RAM') AS memoria,
ROUND(CAST(detalles->>'$.precio' AS DECIMAL(10,2)) * 1.21, 2) AS precio_con_iva
FROM productos
WHERE detalles IS NOT NULL
AND detalles->>'$.precio' IS NOT NULL;| nombre | marca_mayus | memoria | precio_con_iva |
|---|---|---|---|
| MacBook Air M3 | APPLE | 16GB RAM | 1692.69 |
| Galaxy S24 Ultra | SAMSUNG | 12GB RAM | 1765.59 |
| ThinkPad X1 | LENOVO | 32GB RAM | 1874.29 |
Los valores extraídos con ->> son cadenas SQL normales que puedes manipular con cualquier función de cadenas, fechas o números (aplicando las conversiones de tipo necesarias).
En el siguiente artículo veremos JSON_VALUE como alternativa tipada para extraer valores escalares.
Escrito por Eduardo Lázaro
