jsonb_set devuelve una copia de un documento JSON con el valor de una ruta dada reemplazado por uno nuevo. Es el caballo de batalla de las actualizaciones parciales: en lugar de reescribir todo el JSON desde la aplicacion, cambias un solo campo anidado directamente en la base de datos y dejas intactas las demas claves.
Sintaxis y la ruta
La firma es jsonb_set(target, path, new_value [, create_missing]). La ruta es un array de claves de texto que recorre el documento hasta el punto que quieres cambiar.
SELECT jsonb_set(
'{"address": {"city": "Quito", "zip": "170150"}}'::jsonb,
'{address,city}',
'"Lima"'
);
Algunos detalles que conviene tener claros:
path se escribe como literal de array: '{address,city}' significa "clave address, y dentro de ella la clave city".
new_value debe ser JSON valido. La cadena "Lima" tiene que llevar sus comillas; un Lima suelto provoca un error.
- Puedes incluir indices de array en la ruta:
'{tags,0}' es el primer elemento del array tags.
- La funcion solo opera sobre
jsonb, no sobre json. Convierte la columna con ::jsonb si hace falta.
El flag create_missing
El cuarto argumento controla que pasa cuando la clave aun no esta en el documento. Su valor por defecto es true, asi que una clave ausente se crea.
SELECT jsonb_set(
'{"city": "Lima"}'::jsonb,
'{verified}',
'true',
true
);
SELECT jsonb_set(
'{"city": "Lima"}'::jsonb,
'{verified}',
'true',
false
);
Trampa: create_missing solo crea la clave final de la ruta. Si un objeto intermedio no existe (por ejemplo no hay ninguna clave address y escribes en '{address,city}'), la ruta es inalcanzable y el documento vuelve sin cambios y sin error alguno. Eso parece un exito silencioso, asi que verifica el resultado.
Actualizar un campo anidado en un UPDATE
El caso estrella es corregir un campo directamente en una tabla. Supongamos que users tiene una columna JSONB profile y necesitas cambiar la ciudad de un usuario:
UPDATE users
SET profile = jsonb_set(profile, '{address,city}', '"Lima"')
WHERE id = 42;
Como jsonb_set devuelve una copia, asignas esa copia de vuelta a la columna. El resto de profile queda como estaba.
Las llamadas se anidan, asi que puedes cambiar varios campos en una sola pasada:
UPDATE users
SET profile = jsonb_set(
jsonb_set(profile, '{address,city}', '"Lima"'),
'{verified}', 'true'
)
WHERE country = 'PE';
Para numeros, inserta un numero real sin comillas: jsonb_set(data, '{score}', '10') produce 10, mientras que '"10"' produce la cadena "10". Un valor dinamico se arma comodo con to_jsonb:
UPDATE orders
SET meta = jsonb_set(meta, '{discount}', to_jsonb(amount * 0.1))
WHERE status = 'paid';
Eliminar claves con el operador menos
Para borrar un campo no necesitas jsonb_set en absoluto; lo hace el operador -. Elimina una clave de nivel superior o un elemento de array por indice.
SELECT '{"city": "Lima", "tmp": 1}'::jsonb - 'tmp';
SELECT '["a", "b", "c"]'::jsonb - 1;
Para una clave anidada se usa el operador #-, al que se le pasa una ruta:
UPDATE users
SET profile = profile #- '{address,zip}'
WHERE id = 42;
Varias claves de nivel superior se borran de golpe con un array:
SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - '{a,b}'::text[];
Diferencias en otros motores
PostgreSQL es el motor mas comodo para este trabajo, pero la sintaxis cambia en otros:
- MySQL usa
JSON_SET(col, '$.address.city', 'Lima') con una ruta en forma de cadena $.a.b. Tambien tiene JSON_REMOVE para borrar y JSON_REPLACE, que nunca crea claves ausentes, justo el comportamiento de create_missing = false.
- ClickHouse no esta pensado para actualizaciones parciales de JSON: sus funciones JSON son sobre todo de lectura, y cambiar un documento suele implicar reescribir todo el valor.
La regla practica para PostgreSQL: jsonb_set siempre devuelve un documento nuevo, asi que solo funciona junto a una asignacion (SET col = jsonb_set(...)); para borrar recurre a - y #-.
jsonb_setdevuelve una copia de un documento JSON con el valor de una ruta dada reemplazado por uno nuevo. Es el caballo de batalla de las actualizaciones parciales: en lugar de reescribir todo el JSON desde la aplicacion, cambias un solo campo anidado directamente en la base de datos y dejas intactas las demas claves.Sintaxis y la ruta
La firma es
jsonb_set(target, path, new_value [, create_missing]). La ruta es un array de claves de texto que recorre el documento hasta el punto que quieres cambiar.SELECT jsonb_set( '{"address": {"city": "Quito", "zip": "170150"}}'::jsonb, '{address,city}', '"Lima"' ); -- {"address": {"city": "Lima", "zip": "170150"}}Algunos detalles que conviene tener claros:
pathse escribe como literal de array:'{address,city}'significa "clave address, y dentro de ella la clave city".new_valuedebe ser JSON valido. La cadena"Lima"tiene que llevar sus comillas; unLimasuelto provoca un error.'{tags,0}'es el primer elemento del arraytags.jsonb, no sobrejson. Convierte la columna con::jsonbsi hace falta.El flag create_missing
El cuarto argumento controla que pasa cuando la clave aun no esta en el documento. Su valor por defecto es
true, asi que una clave ausente se crea.-- key "verified" is absent; with create_missing = true (default) it is added SELECT jsonb_set( '{"city": "Lima"}'::jsonb, '{verified}', 'true', true ); -- {"city": "Lima", "verified": true} -- with create_missing = false the document is returned unchanged SELECT jsonb_set( '{"city": "Lima"}'::jsonb, '{verified}', 'true', false ); -- {"city": "Lima"}Actualizar un campo anidado en un UPDATE
El caso estrella es corregir un campo directamente en una tabla. Supongamos que
userstiene una columna JSONBprofiley necesitas cambiar la ciudad de un usuario:UPDATE users SET profile = jsonb_set(profile, '{address,city}', '"Lima"') WHERE id = 42;Como
jsonb_setdevuelve una copia, asignas esa copia de vuelta a la columna. El resto deprofilequeda como estaba.Las llamadas se anidan, asi que puedes cambiar varios campos en una sola pasada:
UPDATE users SET profile = jsonb_set( jsonb_set(profile, '{address,city}', '"Lima"'), '{verified}', 'true' ) WHERE country = 'PE';Para numeros, inserta un numero real sin comillas:
jsonb_set(data, '{score}', '10')produce10, mientras que'"10"'produce la cadena"10". Un valor dinamico se arma comodo conto_jsonb:UPDATE orders SET meta = jsonb_set(meta, '{discount}', to_jsonb(amount * 0.1)) WHERE status = 'paid';Eliminar claves con el operador menos
Para borrar un campo no necesitas
jsonb_seten absoluto; lo hace el operador-. Elimina una clave de nivel superior o un elemento de array por indice.-- remove a top-level key SELECT '{"city": "Lima", "tmp": 1}'::jsonb - 'tmp'; -- {"city": "Lima"} -- remove an array element by index SELECT '["a", "b", "c"]'::jsonb - 1; -- ["a", "c"]Para una clave anidada se usa el operador
#-, al que se le pasa una ruta:UPDATE users SET profile = profile #- '{address,zip}' WHERE id = 42;Varias claves de nivel superior se borran de golpe con un array:
SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - '{a,b}'::text[]; -- {"c": 3}Diferencias en otros motores
PostgreSQL es el motor mas comodo para este trabajo, pero la sintaxis cambia en otros:
JSON_SET(col, '$.address.city', 'Lima')con una ruta en forma de cadena$.a.b. Tambien tieneJSON_REMOVEpara borrar yJSON_REPLACE, que nunca crea claves ausentes, justo el comportamiento decreate_missing = false.La regla practica para PostgreSQL:
jsonb_setsiempre devuelve un documento nuevo, asi que solo funciona junto a una asignacion (SET col = jsonb_set(...)); para borrar recurre a-y#-.