Periksa apakah array Postgres JSON berisi string

120

Saya memiliki meja untuk menyimpan informasi tentang kelinci saya. Ini terlihat seperti ini:

create table rabbits (rabbit_id bigserial primary key, info json not null);
insert into rabbits (info) values
  ('{"name":"Henry", "food":["lettuce","carrots"]}'),
  ('{"name":"Herald","food":["carrots","zucchini"]}'),
  ('{"name":"Helen", "food":["lettuce","cheese"]}');

Bagaimana cara menemukan kelinci yang suka wortel? Saya datang dengan ini:

select info->>'name' from rabbits where exists (
  select 1 from json_array_elements(info->'food') as food
  where food::text = '"carrots"'
);

Saya tidak suka kueri itu. Ini berantakan.

Sebagai pemelihara kelinci penuh waktu, saya tidak punya waktu untuk mengubah skema database saya. Saya hanya ingin memberi makan kelinci saya dengan benar. Apakah ada cara yang lebih mudah dibaca untuk melakukan kueri itu?

Semakin bertambah
sumber
1
Pertanyaan menarik. Saya telah bermain-main dengannya, tetapi kemudian saya sadar, saya tidak yakin apa yang Anda maksud dengan "lebih baik". Kriteria apa yang Anda gunakan untuk menilai jawaban Anda? Keterbacaan? Efisiensi? Lain?
David S
@ DavidS: (Saya memperbarui pertanyaannya.) Saya lebih suka keterbacaan daripada efisiensi. Saya tentu tidak mengharapkan sesuatu yang lebih baik daripada pemindaian tabel lengkap, karena saya menahan skema tetap.
Bola Salju
11
Apakah salah jika saya memberi suara positif pada pertanyaan ini karena kelinci?
osman
3
Saya baru saja memberikan suara positif untuk pertanyaan ini karena kelinci dan kemudian melihat komentar Anda @osman
1valdis
Saya melihat komentar Anda dan kemudian menyadari bahwa saya perlu memberi suara positif karena kelinci
Peter Aron Zentai

Jawaban:

185

Pada PostgreSQL 9.4, Anda dapat menggunakan ?operator :

select info->>'name' from rabbits where (info->'food')::jsonb ? 'carrots';

Anda bahkan dapat mengindeks ?kueri pada "food"kunci jika Anda beralih ke tipe jsonb sebagai gantinya:

alter table rabbits alter info type jsonb using info::jsonb;
create index on rabbits using gin ((info->'food'));
select info->>'name' from rabbits where info->'food' ? 'carrots';

Tentu saja, Anda mungkin tidak punya waktu untuk itu sebagai pemelihara kelinci penuh waktu.

Pembaruan: Berikut adalah demonstrasi peningkatan kinerja di atas meja 1.000.000 kelinci di mana setiap kelinci menyukai dua makanan dan 10% dari mereka menyukai wortel:

d=# -- Postgres 9.3 solution
d=# explain analyze select info->>'name' from rabbits where exists (
d(# select 1 from json_array_elements(info->'food') as food
d(#   where food::text = '"carrots"'
d(# );
 Execution time: 3084.927 ms

d=# -- Postgres 9.4+ solution
d=# explain analyze select info->'name' from rabbits where (info->'food')::jsonb ? 'carrots';
 Execution time: 1255.501 ms

d=# alter table rabbits alter info type jsonb using info::jsonb;
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 465.919 ms

d=# create index on rabbits using gin ((info->'food'));
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 256.478 ms
Semakin bertambah
sumber
bagaimana cara mendapatkan baris di mana array makanan di dalam json tidak kosong, misalnya jika kita dapat mempertimbangkan, mereka adalah JSON, di mana array makanan juga kosong, dapatkah Anda membantu
Bravo
1
@Bravoselect * from rabbits where info->'food' != '[]';
Bola Salju
1
Adakah yang tahu bagaimana ini bekerja jika Anda perlu memilih bilangan bulat daripada string / teks?
Rotareti
3
@Rotareti Anda dapat menggunakan @> operator : create table t (x jsonb); insert into t (x) values ('[1,2,3]'), ('[2,3,4]'), ('[3,4,5]'); select * from t where x @> '2';. Perhatikan bahwa itu '2'adalah nomor JSON; jangan disesatkan oleh kutipannya.
Bola Salju
@Snowball, kueri ini pilih info - >> 'nama' dari kelinci di mana (info -> 'makanan') :: jsonb? 'wortel'; berfungsi sempurna untuk kata pencarian dari JSON. Tapi bagaimana caranya agar semua catatan tidak mengandung kata 'wortel'?
Milan
23

Anda dapat menggunakan operator @> untuk melakukan ini seperti

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
gori
sumber
1
Ini berguna ketika item tersebut juga nol
Lucio
2
Pastikan Anda memperhatikan tanda 'centang di sekitar "wortel" ... wortel rusak jika Anda membiarkannya, bahkan jika Anda memeriksa bilangan bulat. (menghabiskan 3 jam mencoba menemukan bilangan bulat, membuatnya bekerja secara ajaib dengan membungkus 'kutu di sekitar angka)
skplunkerin
@skplunkerin Ini harus berupa nilai json yang diapit tanda centang 'untuk membentuk string, karena semuanya adalah string untuk SQL dalam tipe JSONB. Sebagai contoh, boolean: 'true', String: '"example"', integer: '123'.
1valdis
22

Tidak lebih pintar tapi lebih sederhana:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';
chrmod.dll
sumber
13

Variasi kecil tapi tidak ada yang baru. Benar-benar kehilangan fitur ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
macias
sumber