JSON_MERGE_PRESERVE
La función JSON_MERGE_PRESERVE combina dos o más documentos JSON preservando todos los valores, sin descartar ninguno. Cuando dos objetos tienen la misma clave, en lugar de reemplazar el valor anterior (como hace JSON_MERGE_PATCH), JSON_MERGE_PRESERVE combina ambos valores en un array. Y cuando combina arrays, los concatena. Esta semántica de "conservar todo" la convierte en la función ideal para recopilar datos de múltiples fuentes, agregar listas de elementos, o construir historiales donde ningún valor debe perderse.
Sintaxis
JSON_MERGE_PRESERVE(json_doc, json_doc [, json_doc ...])La función recibe dos o más documentos JSON y los combina de izquierda a derecha. Las reglas de combinación dependen de los tipos de los valores involucrados: si ambos son arrays, se concatenan; si ambos son objetos, se fusionan recursivamente combinando los valores de claves duplicadas; si uno o ambos son escalares, se convierten en array y se combinan. El resultado preserva absolutamente todos los valores de todos los documentos.
JSON_MERGE_PRESERVE(doc1, doc2)
JSON_MERGE_PRESERVE(doc1, doc2, doc3)Comportamiento básico
Veamos cómo se comporta la función con arrays. Cuando combinas dos arrays, se concatenan:
SELECT JSON_MERGE_PRESERVE(
'["rojo", "azul"]',
'["verde", "amarillo"]'
) AS resultado;| resultado |
|---|
| ["rojo", "azul", "verde", "amarillo"] |
Cuando combinas dos objetos con claves distintas, el resultado incluye todas las claves de ambos:
SELECT JSON_MERGE_PRESERVE(
'{"nombre": "iPhone", "precio": 1299}',
'{"color": "negro", "stock": 45}'
) AS resultado;| resultado |
|---|
| {"color": "negro", "nombre": "iPhone", "precio": 1299, "stock": 45} |
La diferencia clave con JSON_MERGE_PATCH aparece cuando los objetos tienen claves en común. En lugar de reemplazar, JSON_MERGE_PRESERVE combina los valores en un array:
SELECT JSON_MERGE_PRESERVE(
'{"nombre": "iPhone", "tag": "premium"}',
'{"nombre": "iPhone 15 Pro", "tag": "5g"}'
) AS resultado;| resultado |
|---|
| {"nombre": ["iPhone", "iPhone 15 Pro"], "tag": ["premium", "5g"]} |
Ambos valores de nombre y tag se conservaron dentro de arrays. Ningún dato se perdió.
Cuando un valor ya es un array y el otro es un escalar, el escalar se añade al array:
SELECT JSON_MERGE_PRESERVE(
'{"tags": ["premium", "apple"]}',
'{"tags": "5g"}'
) AS resultado;| resultado |
|---|
| {"tags": ["premium", "apple", "5g"]} |
Y cuando ambos valores son arrays, se concatenan:
SELECT JSON_MERGE_PRESERVE(
'{"tags": ["premium", "apple"]}',
'{"tags": ["5g", "smartphone"]}'
) AS resultado;| resultado |
|---|
| {"tags": ["premium", "apple", "5g", "smartphone"]} |
Caso práctico: recopilar etiquetas de múltiples fuentes
Imagina que diferentes departamentos de una empresa etiquetan los mismos productos con clasificaciones distintas. Marketing pone etiquetas comerciales, almacén pone etiquetas logísticas, y el departamento técnico pone etiquetas de especificaciones:
CREATE TABLE etiquetado (
id INT PRIMARY KEY AUTO_INCREMENT,
producto_id INT,
departamento VARCHAR(50),
etiquetas JSON
);
INSERT INTO etiquetado (producto_id, departamento, etiquetas) VALUES
(1, 'Marketing', '{"tags": ["premium", "bestseller"], "público": "profesional"}'),
(1, 'Almacén', '{"tags": ["frágil", "electrónica"], "ubicación": "A3-15"}'),
(1, 'Técnico', '{"tags": ["5g", "oled"], "público": "avanzado"}');Para consolidar todas las etiquetas del producto 1 en un solo documento:
SELECT JSON_MERGE_PRESERVE(
'{"tags": ["premium", "bestseller"], "público": "profesional"}',
'{"tags": ["frágil", "electrónica"], "ubicación": "A3-15"}',
'{"tags": ["5g", "oled"], "público": "avanzado"}'
) AS consolidado;| consolidado |
|---|
| {"público": ["profesional", "avanzado"], "tags": ["premium", "bestseller", "frágil", "electrónica", "5g", "oled"], "ubicación": "A3-15"} |
Observa que los arrays de tags se concatenaron (6 etiquetas en total), los valores de público se combinaron en un array (ambos departamentos tenían esa clave), y ubicación se conservó tal cual porque solo existía en un documento.
Caso práctico: historial de cambios
JSON_MERGE_PRESERVE es perfecto para construir historiales donde quieres acumular valores sin perder ninguno. Imagina un registro de acciones realizadas sobre un pedido:
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente VARCHAR(100),
historial JSON DEFAULT ('{"eventos": [], "notas": []}')
);
INSERT INTO pedidos (id, cliente, historial) VALUES
(1, 'María García', '{"eventos": ["creado"], "notas": ["Pedido urgente"]}');Cada vez que se realiza una acción, puedes acumular eventos usando JSON_MERGE_PRESERVE:
UPDATE pedidos
SET historial = JSON_MERGE_PRESERVE(
historial,
'{"eventos": ["pago_confirmado"], "notas": ["Pago con tarjeta Visa"]}'
)
WHERE id = 1;
UPDATE pedidos
SET historial = JSON_MERGE_PRESERVE(
historial,
'{"eventos": ["enviado"], "notas": ["Transportista: SEUR"]}'
)
WHERE id = 1;Después de las actualizaciones:
SELECT JSON_PRETTY(historial) AS historial
FROM pedidos
WHERE id = 1;| historial |
|---|
| {"eventos": ["creado", "pago_confirmado", "enviado"], "notas": ["Pedido urgente", "Pago con tarjeta Visa", "Transportista: SEUR"]} |
Todos los eventos y notas se acumularon cronológicamente sin perder ninguno. Este patrón evita la necesidad de una tabla separada de eventos para casos simples.
Caso práctico: combinación de resultados de búsqueda
Cuando consultas datos de múltiples tablas y necesitas consolidar los resultados en un solo documento JSON:
CREATE TABLE productos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100),
datos_basicos JSON
);
CREATE TABLE inventario (
producto_id INT PRIMARY KEY,
datos_stock JSON
);
INSERT INTO productos VALUES
(1, 'iPhone 15 Pro', '{"precio": 1299, "categoría": "smartphone"}'),
(2, 'MacBook Air M3', '{"precio": 1399, "categoría": "portátil"}');
INSERT INTO inventario VALUES
(1, '{"stock": 45, "almacén": "Madrid", "alertas": ["stock bajo en Barcelona"]}'),
(2, '{"stock": 25, "almacén": "Barcelona", "alertas": []}');SELECT
p.nombre,
JSON_MERGE_PRESERVE(p.datos_basicos, i.datos_stock) AS datos_completos
FROM productos p
JOIN inventario i ON p.id = i.producto_id;| nombre | datos_completos |
|---|---|
| iPhone 15 Pro | {"alertas": ["stock bajo en Barcelona"], "almacén": "Madrid", "categoría": "smartphone", "precio": 1299, "stock": 45} |
| MacBook Air M3 | {"alertas": [], "almacén": "Barcelona", "categoría": "portátil", "precio": 1399, "stock": 25} |
Como las claves de ambos documentos son diferentes, el resultado es una fusión limpia sin arrays inesperados.
Manejo de NULL
Cuando un argumento es SQL NULL, JSON_MERGE_PRESERVE devuelve NULL:
SELECT JSON_MERGE_PRESERVE(NULL, '{"a": 1}') AS resultado;| resultado |
|---|
| NULL |
A diferencia de JSON_MERGE_PATCH, el JSON null no elimina claves. En su lugar, se trata como un valor más que se preserva:
SELECT JSON_MERGE_PRESERVE(
'{"a": 1}',
'{"a": null}'
) AS resultado;| resultado |
|---|
| {"a": [1, null]} |
El valor null se añadió al array junto con el valor original 1. Esto refuerza la filosofía de "preservar todo".
Combinación con otras funciones
JSON_MERGE_PRESERVE es particularmente útil dentro de agregaciones. Puedes usarla con variables de sesión o en subconsultas para consolidar múltiples filas:
SELECT
e.producto_id,
JSON_MERGE_PRESERVE(
JSON_OBJECT('departamento', MIN(e.departamento)),
JSON_OBJECT('total_registros', COUNT(*))
) AS resumen
FROM etiquetado e
GROUP BY e.producto_id;Para comparar visualmente JSON_MERGE_PRESERVE con JSON_MERGE_PATCH:
SELECT
JSON_MERGE_PRESERVE('{"a": 1}', '{"a": 2}') AS preserve_result,
JSON_MERGE_PATCH('{"a": 1}', '{"a": 2}') AS patch_result;| preserve_result | patch_result |
|---|---|
| {"a": [1, 2]} | {"a": 2} |
La elección entre ambas funciones depende de tu objetivo: si quieres que el segundo documento actualice al primero, usa JSON_MERGE_PATCH. Si quieres conservar todos los valores de ambos documentos, usa JSON_MERGE_PRESERVE. En el siguiente artículo veremos JSON_ARRAY_APPEND para añadir elementos a arrays JSON.
Escrito por Eduardo Lázaro
