Como unnest e GROUP BY elementos de uma matriz JSON?

8

Dada a bandtabela, com uma jsoncoluna segurando uma matriz:

id | people
---+-------------
1  | ['John', 'Thomas']
2  | ['John', 'James']
3  | ['James', 'George']

Como listar o número de bandas das quais cada nome faz parte?
Saída desejada:

name   | count
-------+------------
John   | 2
James  | 2
Thomas | 1
George | 1
Bax
fonte

Respostas:

7

O tipo de dados da coluna peopleé json, como é o resultado de json_array_elements(people). E não há operador de igualdade ( =) para o tipo de dados json. Então você também não pode executar GROUP BYisso. Mais:

jsonbpossui um operador de igualdade, portanto, a "solução alternativa" em sua resposta é converter jsonbe usar o equivalente jsonb_array_elements(). O elenco acrescenta custo:

jsonb_array_elements(people::jsonb)

Desde o Postgres 9.4, também temos json_array_elements_text(json)elementos de matriz retornados como text. Relacionado:

Assim:

SELECT p.name, count(*) AS c
FROM   band b, json_array_elements_text(b.people) p(name)
GROUP  BY p.name;

Parece mais conveniente obter nomes como em textvez de jsonbobjetos (aspas duplas na representação de texto) e sua "saída desejada" indica que você deseja / precisa textno resultado para começar.

GROUP BYnos textdados também é mais barato do que nos jsonb, portanto, essa "solução alternativa" deve ser mais rápida por dois motivos. (Teste com EXPLAIN (ANALYZE, TIMING OFF).)

Para o registro, não havia nada errado com sua resposta original . A vírgula ( ,) é tão "correta" quanto CROSS JOIN LATERAL. Tendo sido definido anteriormente no SQL padrão, não o torna inferior. Vejo:

Também não é mais portável para outros RDBMS, e uma vez que jsonb_array_elements()ou json_array_elements_text()não são portáveis para outros RDBMS, para começar, que também é irrelevante. A consulta curta não fica mais clara com a CROSS JOIN LATERALIMO, mas a última parte é apenas minha opinião pessoal.

Usei o alias de tabela e coluna mais explícito p(name)e a referência qualificada p.namepara se defender contra possíveis nomes duplicados. nameé uma palavra tão comum, também pode aparecer como nome da coluna na tabela subjacente band; nesse caso, ela seria resolvida silenciosamente band.name. O formulário simples json_array_elements_text(people) nameanexa apenas um alias da tabela ; o nome da coluna ainda é value, conforme retornado da função. Mas nameresolve para sua única coluna valuequando usado na SELECTlista. Ele passa a trabalhar como esperado . Mas um nome de coluna verdadeiro name(se band.nameexistir) seria vinculado primeiro. Enquanto isso não vai morder no exemplo dado, pode ser uma arma de pé carregada em outros casos.

Não use o "nome" genérico como identificador para começar. Talvez fosse apenas para o caso de teste simples.


Se a coluna peoplepuder conter algo além de uma matriz JSON simples , qualquer uma dessas consultas acionará uma exceção. Se você não pode garantir a integridade dos dados, convém se defender com json_typeof():

SELECT p.name, count(*) AS c
FROM   band b, json_array_elements_text(b.people) p(name)
WHERE  json_typeof(b.people) = 'array'
GROUP  BY 1; -- optional short syntax since you seem to prefer short syntax

Exclui violar linhas da consulta.

Relacionado:

Erwin Brandstetter
fonte
4

Baseado no comentário do ypercubeᵀᴹ, eu terminei com:

SELECT name, count(*) as c
FROM band 
CROSS JOIN LATERAL jsonb_array_elements(people::jsonb) as name
GROUP BY name;

Apenas usado em jsonb_array_elementsvez de unnest.

Bax
fonte
-1

Para alguém no MySQL

SELECT
  JSON_EXTRACT(people, CONCAT('$[', idx, ']')) AS name, count(*) as count
FROM yourtable
JOIN subtable AS indexes
WHERE JSON_EXTRACT(people, CONCAT('$[', idx, '].id')) IS NOT NULL
group by name

com subtable como: Colum: idx, linha: 0,1,2,3,4,5,6,7,8,9 ...

Long Aivy
fonte