El operador @> le hace una sola pregunta a un valor JSONB: "?contienes este fragmento?". No es igualdad ni extraccion de campo, sino una prueba de contencion: el documento de la izquierda debe incluir todo lo descrito a la derecha, a cualquier profundidad. Eso es mas compacto que una cadena de condiciones ->> y, sobre todo, puede apoyarse en un indice GIN.
Que significa "contiene"
payload @> '{"plan":"pro"}' es verdadero cuando payload tiene una clave plan con valor "pro" -- aunque haya decenas de otras claves al lado. El operando derecho es un fragmento plantilla, no un documento completo.
Supongamos que users tiene una columna payload jsonb con ajustes y etiquetas:
SELECT id, email
FROM users
WHERE payload @> '{"plan":"pro"}';
Algunas reglas que conviene recordar:
- La coincidencia es por subconjunto: el documento puede llevar cualquier cosa mas alla del fragmento.
- Puedes indicar varias condiciones a la vez:
payload @> '{"plan":"pro","active":true}' exige ambas claves.
- Los tipos deben coincidir estrictamente:
'{"active":true}' no coincide con "active":"true" (cadena frente a booleano).
- El operador
<@ es el espejo: a <@ b significa "a esta contenido en b".
Objetos anidados y arrays
La fuerza de @> es que el fragmento puede estar anidado y la coincidencia es recursiva. Pongamos lineas de pedido y una direccion de envio dentro de un pedido:
SELECT id, amount
FROM orders
WHERE payload @> '{"shipping":{"country":"DE"}}';
No importa que shipping tambien guarde city y zip -- basta con que country sea igual a "DE".
Para los arrays, @> comprueba la pertenencia, no la posicion ni el orden:
SELECT id
FROM orders
WHERE payload @> '{"items":[{"sku":"A-100"}]}';
SELECT id
FROM users
WHERE payload @> '{"tags":["beta","eu"]}';
Trampa: los escalares de un array coinciden "como un conjunto", y los objetos dentro de un array tambien se comparan por contencion. Asi '[1,2]' @> '[2,1]' es verdadero (el orden da igual), mientras que '[1,2]' @> '[1,2,3]' es falso -- la izquierda no tiene el 3. Y recuerda: un objeto vacio '{}' esta contenido en todo objeto, asi que payload @> '{}' es verdadero casi siempre.
Indice GIN: donde @> realmente gana
Sin indice, @> obliga a un escaneo secuencial. Pero JSONB puede vivir en un indice GIN, y entonces las consultas de contencion se vuelven rapidas:
CREATE INDEX idx_users_payload ON users USING gin (payload);
SELECT id FROM users WHERE payload @> '{"plan":"pro"}';
Un detalle sobre las clases de operador:
- La predeterminada (
jsonb_ops) soporta @>, ?, ?|, ?& e indexa tanto claves como valores.
- La clase
jsonb_path_ops (USING gin (payload jsonb_path_ops)) es mas pequena y rapida para @>, pero no soporta los operadores de existencia de clave como ?.
CREATE INDEX idx_users_payload_path
ON users USING gin (payload jsonb_path_ops);
Verifica el plan con EXPLAIN ANALYZE: si ves un Bitmap Index Scan sobre tu indice GIN, el planificador aprovecho @>.
@> frente a la igualdad con ->>
El filtro clasico extrae un escalar y lo compara:
SELECT id FROM users
WHERE payload ->> 'plan' = 'pro';
Funciona, pero tiene trampas:
->> devuelve text, asi que numeros y booleanos se convierten a cadena; es facil introducir un error de tipos sutil.
- Un B-tree sobre
(payload ->> 'plan') debe crearse por cada clave por separado; @> se sirve con un solo indice GIN sobre toda la columna.
- Para campos anidados
->> se vuelve una escalera payload -> 'shipping' ->> 'country', mientras que @> sigue siendo una sola declaracion.
Regla practica: para comprobar "el documento contiene esta forma", usa @> mas GIN. Para rangos y desigualdades (amount > 100, created_at < ...) @> es inutil -- ahi necesitas ->> con un cast o una columna dedicada.
Otros motores carecen del operador: en MySQL el analogo es la funcion JSON_CONTAINS(payload, '{"plan":"pro"}') que devuelve 0/1, mientras que las comprobaciones de ruta pasan por JSON_EXTRACT/->>. En ClickHouse el JSON suele analizarse con funciones como JSONExtractString, y no hay un unico operador de contencion.
El operador
@>le hace una sola pregunta a un valor JSONB: "?contienes este fragmento?". No es igualdad ni extraccion de campo, sino una prueba de contencion: el documento de la izquierda debe incluir todo lo descrito a la derecha, a cualquier profundidad. Eso es mas compacto que una cadena de condiciones->>y, sobre todo, puede apoyarse en un indice GIN.Que significa "contiene"
payload @> '{"plan":"pro"}'es verdadero cuandopayloadtiene una claveplancon valor"pro"-- aunque haya decenas de otras claves al lado. El operando derecho es un fragmento plantilla, no un documento completo.Supongamos que
userstiene una columnapayload jsonbcon ajustes y etiquetas:SELECT id, email FROM users WHERE payload @> '{"plan":"pro"}';Algunas reglas que conviene recordar:
payload @> '{"plan":"pro","active":true}'exige ambas claves.'{"active":true}'no coincide con"active":"true"(cadena frente a booleano).<@es el espejo:a <@ bsignifica "aesta contenido enb".Objetos anidados y arrays
La fuerza de
@>es que el fragmento puede estar anidado y la coincidencia es recursiva. Pongamos lineas de pedido y una direccion de envio dentro de un pedido:SELECT id, amount FROM orders WHERE payload @> '{"shipping":{"country":"DE"}}';No importa que
shippingtambien guardecityyzip-- basta con quecountrysea igual a"DE".Para los arrays,
@>comprueba la pertenencia, no la posicion ni el orden:-- order whose items array includes a SKU object SELECT id FROM orders WHERE payload @> '{"items":[{"sku":"A-100"}]}'; -- tags array containing both values, in any order SELECT id FROM users WHERE payload @> '{"tags":["beta","eu"]}';Indice GIN: donde
@>realmente ganaSin indice,
@>obliga a un escaneo secuencial. Pero JSONB puede vivir en un indice GIN, y entonces las consultas de contencion se vuelven rapidas:CREATE INDEX idx_users_payload ON users USING gin (payload); -- now this can use the index SELECT id FROM users WHERE payload @> '{"plan":"pro"}';Un detalle sobre las clases de operador:
jsonb_ops) soporta@>,?,?|,?&e indexa tanto claves como valores.jsonb_path_ops(USING gin (payload jsonb_path_ops)) es mas pequena y rapida para@>, pero no soporta los operadores de existencia de clave como?.CREATE INDEX idx_users_payload_path ON users USING gin (payload jsonb_path_ops);Verifica el plan con
EXPLAIN ANALYZE: si ves unBitmap Index Scansobre tu indice GIN, el planificador aprovecho@>.@>frente a la igualdad con->>El filtro clasico extrae un escalar y lo compara:
SELECT id FROM users WHERE payload ->> 'plan' = 'pro';Funciona, pero tiene trampas:
->>devuelvetext, asi que numeros y booleanos se convierten a cadena; es facil introducir un error de tipos sutil.(payload ->> 'plan')debe crearse por cada clave por separado;@>se sirve con un solo indice GIN sobre toda la columna.->>se vuelve una escalerapayload -> 'shipping' ->> 'country', mientras que@>sigue siendo una sola declaracion.Regla practica: para comprobar "el documento contiene esta forma", usa
@>mas GIN. Para rangos y desigualdades (amount > 100,created_at < ...)@>es inutil -- ahi necesitas->>con un cast o una columna dedicada.Otros motores carecen del operador: en MySQL el analogo es la funcion
JSON_CONTAINS(payload, '{"plan":"pro"}')que devuelve 0/1, mientras que las comprobaciones de ruta pasan porJSON_EXTRACT/->>. En ClickHouse el JSON suele analizarse con funciones comoJSONExtractString, y no hay un unico operador de contencion.