sqlpostgresqljsonbjson

Borrar en JSONB en PostgreSQL: los operadores - y #-

Como los operadores - y #- eliminan claves, elementos de array y valores anidados de un JSONB.

2 min de lecturaReferencesql · postgresql · jsonb · json

Un documento JSONB casi nunca es definitivo: claves sueltas, banderas temporales y campos con secretos deben desaparecer antes de que la fila salga hacia el exterior. PostgreSQL trae dos operadores de borrado — - para el nivel superior y #- para una ruta anidada. Ambos devuelven un documento nuevo y nunca modifican el original.

El operador -: una clave o un indice de array

El operando izquierdo es jsonb; el derecho indica que quitar. Una cadena elimina una clave de nivel superior; un entero elimina un elemento del array por su indice, donde un indice negativo cuenta desde el final.

-- drop a single top-level key
SELECT '{"name": "Ann", "temp": true}'::jsonb - 'temp';
-- => {"name": "Ann"}

-- remove an array element by index (zero-based)
SELECT '["a", "b", "c"]'::jsonb - 1;
-- => ["a", "c"]

-- negative index counts from the end
SELECT '["a", "b", "c"]'::jsonb - -1;
-- => ["a", "b"]

Supongamos que users tiene una columna profile jsonb con ajustes. Quitemos una clave temporal en todas las filas:

UPDATE users
SET profile = profile - 'onboarding_temp'
WHERE profile ? 'onboarding_temp';

Borrar varias claves a la vez

Cuando el operando derecho es un array text[], - elimina todas las claves de nivel superior listadas en una sola pasada. Resulta comodo para sanear un documento antes de serializarlo en una respuesta de API.

-- strip several keys at once
SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - '{a,c}'::text[];
-- => {"b": 2}

-- hide sensitive fields before returning a user row
SELECT id, email,
       profile - '{password_hash,internal_notes,ssn}'::text[] AS public_profile
FROM users
WHERE id = 42;

Atencion: la forma con array solo afecta al nivel superior. No alcanza los campos anidados — para eso necesitas #-.

El operador #-: un valor en una ruta anidada

#- recibe a la derecha una ruta como text[] y elimina lo que haya al final de esa ruta. Los elementos de la ruta son nombres de clave e indices de array, en orden de anidamiento.

-- remove a nested key
SELECT '{"user": {"name": "Ann", "secret": "x"}}'::jsonb #- '{user,secret}';
-- => {"user": {"name": "Ann"}}

-- remove a deep array element: orders[0].card
SELECT '{"orders": [{"id": 1, "card": "4111"}]}'::jsonb #- '{orders,0,card}';
-- => {"orders": [{"id": 1}]}

En la practica #- suele limpiar datos de registro en orders.meta jsonb antes de una exportacion:

UPDATE orders
SET meta = meta #- '{payment,raw_card_number}'
WHERE status = 'paid'
  AND meta #> '{payment,raw_card_number}' IS NOT NULL;

Trampas y combinacion con jsonb_set

  • Una clave o ruta inexistente no es un error: el operador devuelve el documento sin cambios. Filtra con ? o #> para no lanzar UPDATEs vacios.
  • - y #- siempre devuelven un nuevo jsonb. Para persistir el cambio necesitas UPDATE ... SET col = col - ....
  • Si un indice GIN cubre la columna JSONB, un UPDATE masivo lo reconstruye — en tablas grandes eso no es nada barato.
  • En una ruta de #- un numero se interpreta como indice de array; asegurate de que el nodo sea realmente un array, o el elemento no se encontrara.

El borrado suele ir junto a jsonb_set: una expresion descarta la parte no deseada, la otra escribe un valor nuevo.

-- drop a secret and set a redaction flag in one statement
UPDATE orders
SET meta = jsonb_set(meta #- '{payment,cvv}', '{redacted}', 'true'::jsonb)
WHERE id = 100;

En MySQL el equivalente es JSON_REMOVE(doc, '$.path'), que acepta una o varias rutas. En ClickHouse las columnas JSON suelen ser inmutables, asi que un borrado se expresa con un INSERT del valor reconstruido en vez de un operador puntual.

Practica con ejercicios reales

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

Abrir el entrenador