Come decomprimere e raggruppare gli elementi di un array JSON?


8

Data la bandtabella, con una jsoncolonna che contiene un array:

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

Come elencare il numero di bande di cui fa parte ciascun nome?
Uscita desiderata:

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

Risposte:


7

Il tipo di dati della colonna peopleè json, come è il risultato di json_array_elements(people). E non esiste un operatore di uguaglianza ( =) per il tipo di dati json. Quindi non puoi nemmeno eseguirlo GROUP BY. Di Più:

jsonbha un operatore di uguaglianza, quindi la "soluzione alternativa" nella tua risposta è lanciare jsonbe usare l'equivalente jsonb_array_elements(). Il cast aggiunge un costo:

jsonb_array_elements(people::jsonb)

Da Postgres 9.4 abbiamo anche json_array_elements_text(json)restituito elementi array come text. Relazionato:

Così:

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

Sembra più conveniente ottenere nomi come textinvece di jsonboggetti (tra virgolette doppie nella rappresentazione del testo) e il tuo "output desiderato" indica che vuoi / necessiti textnel risultato per cominciare.

GROUP BYsui textdati è anche più economico che su jsonb, quindi questa "soluzione alternativa" dovrebbe essere più veloce per due motivi. (Prova con EXPLAIN (ANALYZE, TIMING OFF).)

Per la cronaca, non c'era niente di sbagliato nella risposta originale . La virgola ( ,) è "corretta" come CROSS JOIN LATERAL. Essere stato definito in precedenza in SQL standard non lo rende inferiore. Vedere:

Né è più portabile su altri RDBMS, e dato che jsonb_array_elements()o json_array_elements_text()non sono portabili su altri RDBMS per cominciare, anche questo è irrilevante. La breve query non è più chiara con CROSS JOIN LATERALIMO, ma l'ultimo bit è solo la mia opinione personale.

Ho usato l'alias di tabella e colonna più esplicito p(name)e il riferimento qualificato p.nameper la tabella per difendermi da possibili nomi duplicati. nameè una parola così comune, potrebbe anche apparire come nome di colonna nella tabella sottostante band, nel qual caso si risolverebbe silenziosamente band.name. Il modulo semplice json_array_elements_text(people) nameallega solo un alias di tabella , il nome della colonna è fermo value, come restituito dalla funzione. Ma si namerisolve nella sua singola colonna valuequando viene utilizzato SELECTnell'elenco. E capita a funzionare come previsto . Ma un vero nome di colonna name(se band.namedovesse esistere) si legherà per primo. Anche se questo non morde nell'esempio dato, in altri casi può essere una pistola a pedale carica .

Non utilizzare il "nome" generico come identificatore per cominciare. Forse era solo per il semplice test case.


Se la colonna peoplepuò contenere qualsiasi cosa tranne un semplice array JSON , entrambe le query genererebbero un'eccezione. Se non puoi garantire l'integrità dei dati, potresti voler difendere con 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

Esclude le righe di violazione dalla query.

Relazionato:


4

Basato sul commento di @ ypercubeᵀᴹ ho finito con:

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

Appena usato jsonb_array_elementsinvece di unnest.


-1

Per qualcuno in 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

con sottotabella come: Colum: idx, riga: 0,1,2,3,4,5,6,7,8,9 ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.