JSON_CONTAINS_PATH
La función JSON_CONTAINS_PATH verifica si una o más rutas existen dentro de un documento JSON. A diferencia de JSON_CONTAINS, que comprueba si un valor concreto está presente, JSON_CONTAINS_PATH solo se preocupa por la existencia de la estructura: si la clave existe, devuelve verdadero, sin importar qué valor contenga. Esto la convierte en una herramienta ideal para validar la estructura de documentos JSON, comprobar si un campo opcional fue proporcionado, o filtrar registros según los campos que contienen.
Sintaxis
JSON_CONTAINS_PATH(json_doc, tipo_busqueda, ruta [, ruta ...])El primer argumento es el documento JSON que se inspecciona. El segundo es una cadena que indica el modo de búsqueda: 'one' devuelve 1 si al menos una de las rutas existe, mientras que 'all' devuelve 1 solo si todas las rutas proporcionadas existen. A partir del tercer argumento se especifican una o más rutas JSON a verificar. La función devuelve 1 si la condición se cumple, 0 si no se cumple, y NULL si el documento JSON es NULL.
JSON_CONTAINS_PATH(json_doc, 'one', '$.ruta1', '$.ruta2')
JSON_CONTAINS_PATH(json_doc, 'all', '$.ruta1', '$.ruta2')Comportamiento básico
Veamos el comportamiento con un documento JSON sencillo:
SELECT JSON_CONTAINS_PATH(
'{"nombre": "iPhone", "precio": 1299, "color": "negro"}',
'one',
'$.precio'
) AS existe_precio;| existe_precio |
|---|
| 1 |
La ruta $.precio existe en el documento, por lo que devuelve 1. Si buscamos una ruta que no existe:
SELECT JSON_CONTAINS_PATH(
'{"nombre": "iPhone", "precio": 1299}',
'one',
'$.descuento'
) AS existe_descuento;| existe_descuento |
|---|
| 0 |
La diferencia entre los modos 'one' y 'all' se aprecia claramente cuando buscas varias rutas a la vez:
SELECT
JSON_CONTAINS_PATH(
'{"nombre": "iPhone", "precio": 1299}',
'one', '$.precio', '$.descuento'
) AS modo_one,
JSON_CONTAINS_PATH(
'{"nombre": "iPhone", "precio": 1299}',
'all', '$.precio', '$.descuento'
) AS modo_all;| modo_one | modo_all |
|---|---|
| 1 | 0 |
Con 'one', basta con que $.precio exista para devolver 1. Con 'all', como $.descuento no existe, devuelve 0 porque no se cumplen todas las condiciones.
También puedes verificar rutas dentro de estructuras anidadas:
SELECT JSON_CONTAINS_PATH(
'{"especificaciones": {"pantalla": "6.1 pulgadas", "batería": 4000}}',
'one',
'$.especificaciones.pantalla'
) AS existe;| existe |
|---|
| 1 |
Y puedes verificar rutas dentro de arrays usando índices:
SELECT JSON_CONTAINS_PATH(
'{"colores": ["rojo", "azul", "verde"]}',
'one',
'$.colores[2]'
) AS tercer_elemento;| tercer_elemento |
|---|
| 1 |
El array tiene tres elementos (índices 0, 1 y 2), por lo que $.colores[2] existe. Si buscaras $.colores[5], devolvería 0 porque no hay un sexto elemento.
Caso práctico: validación de datos de producto
Imagina una tabla de productos donde cada registro tiene un campo JSON con información variable. No todos los productos incluyen los mismos campos, y necesitas identificar cuáles tienen la información completa:
CREATE TABLE productos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100),
detalles JSON
);
INSERT INTO productos (nombre, detalles) VALUES
('iPhone 15 Pro',
'{"marca": "Apple", "precio": 1299, "peso": 187, "dimensiones": {"alto": 146.6, "ancho": 70.6}, "garantía": 24}'),
('Funda genérica',
'{"precio": 9.99, "color": "negro"}'),
('Samsung Galaxy S24',
'{"marca": "Samsung", "precio": 899, "peso": 167, "dimensiones": {"alto": 147.0, "ancho": 70.6}}'),
('Cable USB-C',
'{"precio": 12.50, "longitud": "1m"}'),
('MacBook Air M3',
'{"marca": "Apple", "precio": 1399, "peso": 1240, "dimensiones": {"alto": 304.1, "ancho": 215.0}, "garantía": 24}');Para encontrar productos que tienen información completa de marca, peso y dimensiones:
SELECT nombre
FROM productos
WHERE JSON_CONTAINS_PATH(detalles, 'all',
'$.marca', '$.peso', '$.dimensiones');| nombre |
|---|
| iPhone 15 Pro |
| Samsung Galaxy S24 |
| MacBook Air M3 |
La funda genérica y el cable USB-C no aparecen porque les faltan esos campos. Para encontrar productos que tienen al menos marca o garantía:
SELECT nombre
FROM productos
WHERE JSON_CONTAINS_PATH(detalles, 'one',
'$.marca', '$.garantía');| nombre |
|---|
| iPhone 15 Pro |
| Samsung Galaxy S24 |
| MacBook Air M3 |
Caso práctico: detección de campos opcionales
Un uso frecuente de JSON_CONTAINS_PATH es detectar qué registros tienen campos opcionales para procesarlos de forma diferente. Considera una tabla de pedidos donde la información de envío puede variar:
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente VARCHAR(100),
datos_envio JSON
);
INSERT INTO pedidos (cliente, datos_envio) VALUES
('María García', '{"dirección": "Calle Mayor 15", "ciudad": "Madrid", "código_postal": "28001", "instrucciones": "Dejar en portería", "teléfono_contacto": "612345678"}'),
('Carlos López', '{"dirección": "Av. Diagonal 200", "ciudad": "Barcelona", "código_postal": "08018"}'),
('Ana Martínez', '{"dirección": "Gran Vía 50", "ciudad": "Valencia", "código_postal": "46002", "instrucciones": "Llamar al timbre 3B"}');Para identificar pedidos que incluyen instrucciones especiales de entrega:
SELECT
cliente,
CASE
WHEN JSON_CONTAINS_PATH(datos_envio, 'one', '$.instrucciones')
THEN datos_envio->>'$.instrucciones'
ELSE 'Sin instrucciones'
END AS instrucciones
FROM pedidos;| cliente | instrucciones |
|---|---|
| María García | Dejar en portería |
| Carlos López | Sin instrucciones |
| Ana Martínez | Llamar al timbre 3B |
Para contar cuántos pedidos tienen información de contacto adicional:
SELECT
COUNT(*) AS total_pedidos,
SUM(JSON_CONTAINS_PATH(datos_envio, 'one', '$.teléfono_contacto')) AS con_telefono,
SUM(JSON_CONTAINS_PATH(datos_envio, 'one', '$.instrucciones')) AS con_instrucciones
FROM pedidos;| total_pedidos | con_telefono | con_instrucciones |
|---|---|---|
| 3 | 1 | 2 |
Como JSON_CONTAINS_PATH devuelve 1 o 0, puedes usar SUM directamente para contar cuántos registros cumplen la condición.
Caso práctico: validación de estructura en respuestas de API
Cuando almacenas respuestas de APIs externas en columnas JSON, es habitual que la estructura varíe. Puedes usar JSON_CONTAINS_PATH para clasificar las respuestas según su completitud:
CREATE TABLE respuestas_api (
id INT PRIMARY KEY AUTO_INCREMENT,
endpoint VARCHAR(200),
respuesta JSON
);
INSERT INTO respuestas_api (endpoint, respuesta) VALUES
('/api/pago', '{"estado": "completado", "transaccion_id": "TXN-001", "monto": 150.00, "moneda": "EUR", "metodo": "tarjeta"}'),
('/api/pago', '{"estado": "error", "código_error": "E-402", "mensaje": "Fondos insuficientes"}'),
('/api/pago', '{"estado": "pendiente", "transaccion_id": "TXN-003", "monto": 89.99}');SELECT
id,
respuesta->>'$.estado' AS estado,
CASE
WHEN JSON_CONTAINS_PATH(respuesta, 'all', '$.transaccion_id', '$.monto', '$.metodo')
THEN 'Completa'
WHEN JSON_CONTAINS_PATH(respuesta, 'all', '$.código_error', '$.mensaje')
THEN 'Error documentado'
ELSE 'Parcial'
END AS tipo_respuesta
FROM respuestas_api;| id | estado | tipo_respuesta |
|---|---|---|
| 1 | completado | Completa |
| 2 | error | Error documentado |
| 3 | pendiente | Parcial |
Manejo de NULL
Cuando el documento JSON es NULL, la función devuelve NULL:
SELECT JSON_CONTAINS_PATH(NULL, 'one', '$.clave') AS resultado;| resultado |
|---|
| NULL |
Un detalle importante: si una clave existe pero su valor JSON es null, JSON_CONTAINS_PATH devuelve 1 porque la ruta sí existe. La función evalúa estructura, no contenido:
SELECT JSON_CONTAINS_PATH(
'{"nombre": "Test", "descuento": null}',
'one',
'$.descuento'
) AS existe;| existe |
|---|
| 1 |
Esto es útil para distinguir entre "el campo no fue proporcionado" y "el campo fue proporcionado con valor nulo".
Combinación con otras funciones
JSON_CONTAINS_PATH se complementa bien con JSON_EXTRACT para acceder a valores solo cuando sabes que existen:
SELECT
nombre,
CASE
WHEN JSON_CONTAINS_PATH(detalles, 'one', '$.garantía')
THEN CONCAT(detalles->>'$.garantía', ' meses')
ELSE 'No disponible'
END AS garantía
FROM productos;| nombre | garantía |
|---|---|
| iPhone 15 Pro | 24 meses |
| Funda genérica | No disponible |
| Samsung Galaxy S24 | No disponible |
| Cable USB-C | No disponible |
| MacBook Air M3 | 24 meses |
Recuerda que JSON_CONTAINS_PATH verifica si la ruta existe, mientras que JSON_CONTAINS verifica si un valor concreto está presente. Si necesitas saber si un array contiene el valor "rojo", usa JSON_CONTAINS. Si necesitas saber si el campo color existe sin importar su valor, usa JSON_CONTAINS_PATH. En el siguiente artículo veremos JSON_SEARCH para buscar valores de texto dentro de documentos JSON.
Escrito por Eduardo Lázaro
