La funcion jsonb_array_length responde a una sola pregunta: cuantos elementos tiene un array JSON. Suena trivial, pero en la practica falla con gusto con cannot get array length of a non-array en cuanto aparece un objeto, un numero o un null en la columna. Veamos como contar la longitud con seguridad, filtrar filas por el tamano del array y en que se diferencia de cardinality sobre un array SQL nativo.
Contar elementos, lo basico
Supongamos que los pedidos tienen una columna data jsonb donde la clave items guarda un array de lineas del carrito. data->'items' devuelve el array JSONB en si (la flecha ->, no ->>), y jsonb_array_length toma su longitud:
SELECT id,
jsonb_array_length(data->'items') AS item_count
FROM orders;
Algunos hechos que ahorran tiempo:
- El argumento debe ser un array JSONB (
[...]), no un objeto ni un escalar.
- La longitud se cuenta solo en el nivel superior: un array anidado cuenta como un elemento.
- Un array vacio
[] da 0 -- una respuesta valida, no un error.
- Para el tipo
json (sin b) existe la gemela json_array_length.
El error non-array y como protegerse
El dolor principal aparece cuando items falta en algunas filas o guarda algo que no es un array. Entonces la consulta entera estalla:
SELECT jsonb_array_length(data->'items') FROM orders;
Hay tres causas, y todas aparecen en datos reales:
- La clave
items no existe -- la flecha devuelve NULL de SQL, y aqui no hay error, simplemente obtienes NULL.
items guarda un objeto {...} o una cadena -- eso es lo que rompe la funcion.
- El valor es un
null de JSON ('null'::jsonb), que tampoco es un array.
La proteccion fiable es comprobar el tipo con jsonb_typeof antes de llamar a la longitud. Lo mas limpio es envolverlo en un CASE o combinarlo con un filtro:
SELECT id,
CASE
WHEN jsonb_typeof(data->'items') = 'array'
THEN jsonb_array_length(data->'items')
ELSE 0
END AS item_count
FROM orders;
Trampa: en PostgreSQL WHERE y SELECT no garantizan el orden de evaluacion, asi que WHERE jsonb_typeof(...) = 'array' AND jsonb_array_length(...) > 2 podria en teoria intentar calcular la longitud antes de la comprobacion de tipo. En la practica el planificador suele poner primero la comprobacion barata, pero un CASE o una subconsulta con orden explicito es mas seguro. No confies en el cortocircuito de AND para silenciar errores con efectos secundarios.
Filtrar filas por el tamano del array
Una tarea tipica es encontrar pedidos con un carrito "grande". Primero excluye los no-arrays, luego compara la longitud:
SELECT id, user_id, amount
FROM orders
WHERE jsonb_typeof(data->'items') = 'array'
AND jsonb_array_length(data->'items') >= 3;
El mismo truco vale para agregados. Por ejemplo, el tamano medio del carrito solo sobre pedidos validos:
SELECT avg(jsonb_array_length(data->'items')) AS avg_items
FROM orders
WHERE jsonb_typeof(data->'items') = 'array';
Si el array esta mas profundo, extiende la ruta con flechas; la proteccion por tipo no cambia:
SELECT u.id, u.email
FROM users AS u
WHERE jsonb_typeof(u.data->'profile'->'tags') = 'array'
AND jsonb_array_length(u.data->'profile'->'tags') = 0;
Esto selecciona usuarios cuyos tags son exactamente cero (un array vacio), pero especificamente un array, no una clave ausente.
jsonb_array_length frente a cardinality
No confundas un array JSON con un array nativo de PostgreSQL como text[] o int[]. Usan funciones de longitud distintas:
jsonb_array_length(x) -- para un valor array JSONB.
cardinality(x) -- para un array SQL; devuelve el numero total de elementos.
array_length(x, 1) -- la longitud del array SQL en la primera dimension; sobre un array vacio devuelve NULL, no 0.
SELECT cardinality(roles) AS role_count
FROM employees;
SELECT jsonb_array_length(data->'items') AS item_count
FROM orders;
Regla practica: si los datos llegan como documento y la forma es fluida, eso es JSONB -- cuenta con jsonb_array_length protegido por jsonb_typeof. Si el array es uniforme y se filtra a menudo, es candidato a una columna de array nativa con cardinality, que es mas concisa y nunca tropieza con escalares.
Otros motores difieren, asi que no supongas la misma semantica. En MySQL la longitud del array JSON viene de JSON_LENGTH(data, '$.items'), y no lanza el error non-array en absoluto -- pero tampoco mide la longitud de un array. Sobre un escalar (un numero, una cadena, un booleano) JSON_LENGTH devuelve 1, y sobre un objeto devuelve el numero de claves de nivel superior, no "la longitud del valor". Asi, una fila donde items es {"a":1,"b":2} da en silencio 2, y una fila donde es 5 da 1 -- sin error, pero tampoco el tamano del carrito. En ClickHouse usas JSONLength(data, 'items') para JSON, o trabajas con columnas nativas Array(...) y la funcion length(). Porta consultas entre motores con cuidado -- el comportamiento sobre un no-array y sobre una clave ausente difiere en cada uno.
La funcion
jsonb_array_lengthresponde a una sola pregunta: cuantos elementos tiene un array JSON. Suena trivial, pero en la practica falla con gusto concannot get array length of a non-arrayen cuanto aparece un objeto, un numero o unnullen la columna. Veamos como contar la longitud con seguridad, filtrar filas por el tamano del array y en que se diferencia decardinalitysobre un array SQL nativo.Contar elementos, lo basico
Supongamos que los pedidos tienen una columna
data jsonbdonde la claveitemsguarda un array de lineas del carrito.data->'items'devuelve el array JSONB en si (la flecha->, no->>), yjsonb_array_lengthtoma su longitud:SELECT id, jsonb_array_length(data->'items') AS item_count FROM orders;Algunos hechos que ahorran tiempo:
[...]), no un objeto ni un escalar.[]da0-- una respuesta valida, no un error.json(sinb) existe la gemelajson_array_length.El error non-array y como protegerse
El dolor principal aparece cuando
itemsfalta en algunas filas o guarda algo que no es un array. Entonces la consulta entera estalla:-- ERROR: cannot get array length of a non-array SELECT jsonb_array_length(data->'items') FROM orders;Hay tres causas, y todas aparecen en datos reales:
itemsno existe -- la flecha devuelveNULLde SQL, y aqui no hay error, simplemente obtienesNULL.itemsguarda un objeto{...}o una cadena -- eso es lo que rompe la funcion.nullde JSON ('null'::jsonb), que tampoco es un array.La proteccion fiable es comprobar el tipo con
jsonb_typeofantes de llamar a la longitud. Lo mas limpio es envolverlo en unCASEo combinarlo con un filtro:SELECT id, CASE WHEN jsonb_typeof(data->'items') = 'array' THEN jsonb_array_length(data->'items') ELSE 0 END AS item_count FROM orders;Filtrar filas por el tamano del array
Una tarea tipica es encontrar pedidos con un carrito "grande". Primero excluye los no-arrays, luego compara la longitud:
SELECT id, user_id, amount FROM orders WHERE jsonb_typeof(data->'items') = 'array' AND jsonb_array_length(data->'items') >= 3;El mismo truco vale para agregados. Por ejemplo, el tamano medio del carrito solo sobre pedidos validos:
SELECT avg(jsonb_array_length(data->'items')) AS avg_items FROM orders WHERE jsonb_typeof(data->'items') = 'array';Si el array esta mas profundo, extiende la ruta con flechas; la proteccion por tipo no cambia:
SELECT u.id, u.email FROM users AS u WHERE jsonb_typeof(u.data->'profile'->'tags') = 'array' AND jsonb_array_length(u.data->'profile'->'tags') = 0;Esto selecciona usuarios cuyos tags son exactamente cero (un array vacio), pero especificamente un array, no una clave ausente.
jsonb_array_length frente a cardinality
No confundas un array JSON con un array nativo de PostgreSQL como
text[]oint[]. Usan funciones de longitud distintas:jsonb_array_length(x)-- para un valor array JSONB.cardinality(x)-- para un array SQL; devuelve el numero total de elementos.array_length(x, 1)-- la longitud del array SQL en la primera dimension; sobre un array vacio devuelveNULL, no0.-- SQL array stored in a real array column SELECT cardinality(roles) AS role_count FROM employees; -- assumes roles text[] -- JSON array stored inside a jsonb column SELECT jsonb_array_length(data->'items') AS item_count FROM orders;Regla practica: si los datos llegan como documento y la forma es fluida, eso es JSONB -- cuenta con
jsonb_array_lengthprotegido porjsonb_typeof. Si el array es uniforme y se filtra a menudo, es candidato a una columna de array nativa concardinality, que es mas concisa y nunca tropieza con escalares.Otros motores difieren, asi que no supongas la misma semantica. En MySQL la longitud del array JSON viene de
JSON_LENGTH(data, '$.items'), y no lanza el error non-array en absoluto -- pero tampoco mide la longitud de un array. Sobre un escalar (un numero, una cadena, un booleano)JSON_LENGTHdevuelve1, y sobre un objeto devuelve el numero de claves de nivel superior, no "la longitud del valor". Asi, una fila dondeitemses{"a":1,"b":2}da en silencio2, y una fila donde es5da1-- sin error, pero tampoco el tamano del carrito. En ClickHouse usasJSONLength(data, 'items')para JSON, o trabajas con columnas nativasArray(...)y la funcionlength(). Porta consultas entre motores con cuidado -- el comportamiento sobre un no-array y sobre una clave ausente difiere en cada uno.