A funcao jsonb_array_length responde a uma unica pergunta: quantos elementos ha em um array JSON. Parece trivial, mas na pratica ela falha alegremente com cannot get array length of a non-array assim que um objeto, um numero ou um null aparece na coluna. Vamos ver como contar o tamanho com seguranca, filtrar linhas pelo tamanho do array e como isso difere de cardinality em um array SQL nativo.
Contar elementos, o basico
Suponha que os pedidos tenham uma coluna data jsonb onde a chave items guarda um array de itens do carrinho. data->'items' retorna o proprio array JSONB (a seta ->, nao ->>), e jsonb_array_length toma o tamanho dele:
SELECT id,
jsonb_array_length(data->'items') AS item_count
FROM orders;
Alguns fatos que economizam tempo:
- O argumento precisa ser um array JSONB (
[...]), nao um objeto nem um escalar.
- O tamanho e contado apenas no nivel superior: um array aninhado conta como um elemento.
- Um array vazio
[] retorna 0 -- uma resposta valida, nao um erro.
- Para o tipo
json (sem b) existe a gemea json_array_length.
O erro non-array e como se proteger
A dor principal aparece quando items falta em algumas linhas ou guarda algo que nao e um array. Entao a consulta inteira explode:
SELECT jsonb_array_length(data->'items') FROM orders;
Ha tres causas, e todas surgem em dados reais:
- A chave
items nao existe -- a seta retorna NULL de SQL, e aqui nao ha erro, voce apenas recebe NULL.
items guarda um objeto {...} ou uma string -- e isso que quebra a funcao.
- O valor e um
null de JSON ('null'::jsonb), que tambem nao e um array.
A protecao confiavel e verificar o tipo com jsonb_typeof antes de chamar o tamanho. O jeito mais limpo e envolver em um CASE ou combinar com um filtro:
SELECT id,
CASE
WHEN jsonb_typeof(data->'items') = 'array'
THEN jsonb_array_length(data->'items')
ELSE 0
END AS item_count
FROM orders;
Pegadinha: no PostgreSQL WHERE e SELECT nao garantem a ordem de avaliacao, entao WHERE jsonb_typeof(...) = 'array' AND jsonb_array_length(...) > 2 poderia em teoria tentar calcular o tamanho antes da verificacao de tipo. Na pratica o planejador costuma colocar primeiro a verificacao barata, mas um CASE ou uma subconsulta com ordem explicita e mais seguro. Nao confie no curto-circuito do AND para silenciar erros com efeitos colaterais.
Filtrar linhas pelo tamanho do array
Uma tarefa tipica e encontrar pedidos com um carrinho "grande". Primeiro exclua os nao-arrays, depois compare o tamanho:
SELECT id, user_id, amount
FROM orders
WHERE jsonb_typeof(data->'items') = 'array'
AND jsonb_array_length(data->'items') >= 3;
O mesmo truque vale para agregados. Por exemplo, o tamanho medio do carrinho apenas sobre pedidos validos:
SELECT avg(jsonb_array_length(data->'items')) AS avg_items
FROM orders
WHERE jsonb_typeof(data->'items') = 'array';
Se o array esta mais fundo, estenda o caminho com setas; a protecao por tipo continua a mesma:
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;
Isso seleciona usuarios cujas tags sao exatamente zero (um array vazio), mas especificamente um array, nao uma chave ausente.
jsonb_array_length contra cardinality
Nao confunda um array JSON com um array nativo do PostgreSQL como text[] ou int[]. Eles usam funcoes de tamanho diferentes:
jsonb_array_length(x) -- para um valor array JSONB.
cardinality(x) -- para um array SQL; retorna o numero total de elementos.
array_length(x, 1) -- o tamanho do array SQL na primeira dimensao; sobre um array vazio retorna NULL, nao 0.
SELECT cardinality(roles) AS role_count
FROM employees;
SELECT jsonb_array_length(data->'items') AS item_count
FROM orders;
Regra pratica: se os dados chegam como documento e a forma e fluida, isso e JSONB -- conte com jsonb_array_length protegido por jsonb_typeof. Se o array e uniforme e filtrado com frequencia, e candidato a uma coluna de array nativa com cardinality, que e mais concisa e nunca tropeca em escalares.
Outros motores diferem, entao nao presuma a mesma semantica. No MySQL o tamanho do array JSON vem de JSON_LENGTH(data, '$.items'), e ele nao gera o erro non-array de jeito nenhum -- mas tambem nao mede o tamanho de um array. Sobre um escalar (um numero, uma string, um booleano) JSON_LENGTH retorna 1, e sobre um objeto retorna o numero de chaves de nivel superior, nao "o tamanho do valor". Assim, uma linha onde items e {"a":1,"b":2} retorna silenciosamente 2, e uma linha onde e 5 retorna 1 -- sem erro, mas tambem nao o tamanho do carrinho. No ClickHouse voce usa JSONLength(data, 'items') para JSON, ou trabalha com colunas nativas Array(...) e a funcao length(). Porte consultas entre motores com cuidado -- o comportamento sobre um nao-array e sobre uma chave ausente difere em cada um.
A funcao
jsonb_array_lengthresponde a uma unica pergunta: quantos elementos ha em um array JSON. Parece trivial, mas na pratica ela falha alegremente comcannot get array length of a non-arrayassim que um objeto, um numero ou umnullaparece na coluna. Vamos ver como contar o tamanho com seguranca, filtrar linhas pelo tamanho do array e como isso difere decardinalityem um array SQL nativo.Contar elementos, o basico
Suponha que os pedidos tenham uma coluna
data jsonbonde a chaveitemsguarda um array de itens do carrinho.data->'items'retorna o proprio array JSONB (a seta->, nao->>), ejsonb_array_lengthtoma o tamanho dele:SELECT id, jsonb_array_length(data->'items') AS item_count FROM orders;Alguns fatos que economizam tempo:
[...]), nao um objeto nem um escalar.[]retorna0-- uma resposta valida, nao um erro.json(semb) existe a gemeajson_array_length.O erro non-array e como se proteger
A dor principal aparece quando
itemsfalta em algumas linhas ou guarda algo que nao e um array. Entao a consulta inteira explode:-- ERROR: cannot get array length of a non-array SELECT jsonb_array_length(data->'items') FROM orders;Ha tres causas, e todas surgem em dados reais:
itemsnao existe -- a seta retornaNULLde SQL, e aqui nao ha erro, voce apenas recebeNULL.itemsguarda um objeto{...}ou uma string -- e isso que quebra a funcao.nullde JSON ('null'::jsonb), que tambem nao e um array.A protecao confiavel e verificar o tipo com
jsonb_typeofantes de chamar o tamanho. O jeito mais limpo e envolver em umCASEou combinar com um 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 linhas pelo tamanho do array
Uma tarefa tipica e encontrar pedidos com um carrinho "grande". Primeiro exclua os nao-arrays, depois compare o tamanho:
SELECT id, user_id, amount FROM orders WHERE jsonb_typeof(data->'items') = 'array' AND jsonb_array_length(data->'items') >= 3;O mesmo truque vale para agregados. Por exemplo, o tamanho medio do carrinho apenas sobre pedidos validos:
SELECT avg(jsonb_array_length(data->'items')) AS avg_items FROM orders WHERE jsonb_typeof(data->'items') = 'array';Se o array esta mais fundo, estenda o caminho com setas; a protecao por tipo continua a mesma:
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;Isso seleciona usuarios cujas tags sao exatamente zero (um array vazio), mas especificamente um array, nao uma chave ausente.
jsonb_array_length contra cardinality
Nao confunda um array JSON com um array nativo do PostgreSQL como
text[]ouint[]. Eles usam funcoes de tamanho diferentes:jsonb_array_length(x)-- para um valor array JSONB.cardinality(x)-- para um array SQL; retorna o numero total de elementos.array_length(x, 1)-- o tamanho do array SQL na primeira dimensao; sobre um array vazio retornaNULL, nao0.-- 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;Regra pratica: se os dados chegam como documento e a forma e fluida, isso e JSONB -- conte com
jsonb_array_lengthprotegido porjsonb_typeof. Se o array e uniforme e filtrado com frequencia, e candidato a uma coluna de array nativa comcardinality, que e mais concisa e nunca tropeca em escalares.Outros motores diferem, entao nao presuma a mesma semantica. No MySQL o tamanho do array JSON vem de
JSON_LENGTH(data, '$.items'), e ele nao gera o erro non-array de jeito nenhum -- mas tambem nao mede o tamanho de um array. Sobre um escalar (um numero, uma string, um booleano)JSON_LENGTHretorna1, e sobre um objeto retorna o numero de chaves de nivel superior, nao "o tamanho do valor". Assim, uma linha ondeitemse{"a":1,"b":2}retorna silenciosamente2, e uma linha onde e5retorna1-- sem erro, mas tambem nao o tamanho do carrinho. No ClickHouse voce usaJSONLength(data, 'items')para JSON, ou trabalha com colunas nativasArray(...)e a funcaolength(). Porte consultas entre motores com cuidado -- o comportamento sobre um nao-array e sobre uma chave ausente difere em cada um.