Bagaimana cara undest dan GROUP BY elemen array JSON?

8

Diberikan bandtabel, dengan jsonkolom memegang array:

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

Bagaimana cara mendaftarkan jumlah band yang dimiliki setiap nama?
Output yang diinginkan:

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

Jawaban:

7

Jenis data kolom peopleadalah json, seperti hasil json_array_elements(people). Dan tidak ada operator kesetaraan ( =) untuk tipe data json. Jadi Anda juga tidak bisa menjalankannya GROUP BY. Lebih:

jsonbmemiliki operator kesetaraan, jadi "solusi" dalam jawaban Anda adalah untuk jsonbmenggunakan dan menggunakan yang setara jsonb_array_elements(). Para pemain menambah biaya:

jsonb_array_elements(people::jsonb)

Sejak Postgres 9.4 kami juga telah json_array_elements_text(json)mengembalikan elemen array sebagai text. Terkait:

Begitu:

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

Tampaknya lebih mudah untuk mendapatkan nama textdaripada jsonbobjek (dikutip ganda dalam representasi teks) dan "output yang Anda inginkan" menunjukkan Anda ingin / perlu textdalam hasil untuk memulai.

GROUP BYpada textdata juga lebih murah daripada pada jsonb, jadi "solusi" alternatif ini harus lebih cepat karena dua alasan. (Uji dengan EXPLAIN (ANALYZE, TIMING OFF).)

Sebagai catatan, tidak ada yang salah dengan jawaban asli Anda . Koma ( ,) sama "benar" dengan CROSS JOIN LATERAL. Telah didefinisikan sebelumnya dalam standar SQL tidak membuatnya lebih rendah. Lihat:

Juga tidak lebih portabel untuk RDBMS lain, dan karena jsonb_array_elements()atau json_array_elements_text()tidak portabel untuk RDBMS lain untuk memulai, itu juga tidak relevan. Permintaan singkat tidak menjadi lebih jelas dengan CROSS JOIN LATERALIMO, tetapi bit terakhir hanya pendapat pribadi saya.

Saya menggunakan tabel dan kolom yang lebih eksplisit alias p(name)dan referensi yang memenuhi syarat tabel p.nameuntuk mempertahankan terhadap kemungkinan nama duplikat. nameadalah kata yang umum, mungkin juga muncul sebagai nama kolom pada tabel di bawahnya band, yang dalam hal ini akan diselesaikan secara diam-diam band.name. Bentuk sederhana json_array_elements_text(people) namehanya melampirkan alias tabel , nama kolom masih value, seperti yang dikembalikan dari fungsi. Tetapi namememutuskan untuk menggunakan satu kolom valuesaat digunakan dalam SELECTdaftar. Itu terjadi untuk bekerja seperti yang diharapkan . Tetapi nama kolom yang benar name(jika band.nameada) akan mengikat terlebih dahulu. Meskipun itu tidak akan menggigit pada contoh yang diberikan, itu bisa menjadi senjata kaki yang dimuat dalam kasus lain.

Jangan gunakan "nama" generik sebagai pengidentifikasi untuk memulai. Mungkin itu hanya untuk test case sederhana.


Jika kolom peoplebisa menampung apa pun selain array JSON biasa , kueri akan memicu pengecualian. Jika Anda tidak dapat menjamin integritas data, Anda mungkin ingin bertahan dengan 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

Tidak termasuk melanggar baris dari kueri.

Terkait:

Erwin Brandstetter
sumber
4

Berdasarkan pada @ ypercubeᵀᴹ komentar saya berakhir dengan:

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

Hanya digunakan jsonb_array_elementssebagai pengganti unnest.

Bax
sumber
-1

Untuk seseorang di 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

dengan subtable seperti: Colum: idx, baris: 0,1,2,3,4,5,6,7,8,9 ...

Long Aivy
sumber