sqlpostgresqljsonbjson

jsonb_array_length en PostgreSQL: contar elementos de un array JSON sin errores

Como jsonb_array_length cuenta elementos de un array JSON, por que lanza el error non-array, como protegerlo con jsonb_typeof y filtrar filas por tamano.

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

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:

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

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

Practica con ejercicios reales

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

Abrir el entrenador