sqlpostgresqljsonjsonb

JSONB_SET en PostgreSQL: actualizar un campo dentro de un documento JSON

Reemplaza un valor por ruta en JSONB, anade una clave ausente con create_missing, actualiza un campo anidado con UPDATE y elimina claves con el operador menos.

3 min de lecturaReferencesql · postgresql · json · jsonb · update

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"'
);
-- {"address": {"city": "Lima", "zip": "170150"}}

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.

-- 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"}

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.

-- 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:

  • 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 #-.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador