Postgres JSON dizisinin bir dize içerip içermediğini kontrol edin


122

Tavşanlar hakkında bilgi depolamak için bir masam var. Şöyle görünüyor:

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"]}');

Havuç seven tavşanları nasıl bulmalıyım? Ben şunu buldum:

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

Bu sorgu hoşuma gitmedi. Bu bir karmaşa.

Tam zamanlı bir tavşan bakıcısı olarak, veritabanı şemamı değiştirmek için zamanım yok. Sadece tavşanlarımı uygun şekilde beslemek istiyorum. Bu sorguyu yapmanın daha okunaklı bir yolu var mı?


1
İlginç soru. Onunla oynadım, ama sonra anladım, "daha iyi" ile ne demek istediğinden emin değilim. Cevaplarınızı hangi kriterlere göre değerlendiriyorsunuz? Okunabilirlik? Verimlilik? Diğer?
David S

@DavidS: (Soruyu güncelledim.) Verimlilik yerine okunabilirliği tercih ederim. Şemayı sabit tuttuğum için, tam bir tablo taramasından daha iyi bir şey beklemiyorum.
Kartopu

12
Bu soruya tavşanlar yüzünden oy vermiş olmam yanlış mı?
osman

3
Bu soruya tavşanlar yüzünden olumlu oy verdim ve sonra yorumunuzu gördüm @osman
1valdis

Yorumunuzu gördüm ve sonra tavşanlar yüzünden olumlu oy vermem gerektiğini fark ettim
Peter Aron Zentai

Yanıtlar:


187

PostgreSQL 9.4'ten itibaren, ?operatörü kullanabilirsiniz :

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

Bunun yerine jsonb türüne geçerseniz anahtardaki ?sorguyu dizine bile ekleyebilirsiniz :"food"

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';

Tabii ki, muhtemelen tam zamanlı bir tavşan bakıcısı olarak bunun için zamanınız yok.

Güncelleme: Her tavşanın iki yiyeceği sevdiği ve% 10'unun havuç gibi olduğu 1.000.000 tavşandan oluşan bir tabloda performans iyileştirmelerinin bir gösterimi:

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

json içindeki yiyecek dizisinin boş olmadığı satırları nasıl elde edeceğiz, örneğin düşünebilirsek, JSON'ları, yiyecek dizisinin de boş olduğu yerler, yardımcı olabilir misiniz
Bravo

1
@Bravoselect * from rabbits where info->'food' != '[]';
Kartopu

1
Bir dize / metin yerine bir tamsayı seçmeniz gerektiğinde bunun nasıl çalıştığını bilen var mı?
Rotareti

3
@Rotareti kullanabilirsiniz @> operatörü : 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';. Bunun '2'bir JSON numarası olduğunu unutmayın ; alıntılarla yanıltmayın.
Snowball

@Snowball, bu sorgu bilgi seçin - >> 'ad' tavşanlardan burada (bilgi -> 'yiyecek') :: jsonb? 'havuçlar'; JSON'daki arama kelimesi için mükemmel çalışıyor. Ama tüm kayıtları nasıl alabilirim 'havuç' kelimesi içermiyor?
Milano

23

Bunu yapmak için @> operatörünü kullanabilirsiniz.

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';

1
Bu, öğe boş olduğunda da kullanışlıdır
Lucio

2
'"Havuçları" çevreleyen kenelere dikkat ettiğinizden emin olun ... bir tamsayı kontrol etseniz bile, onları dışarıda bırakırsanız kırılır. (bir tamsayı bulmaya çalışmakla ', sayının etrafına işaretler
koyarak

@skplunkerin 'Bir dizge oluşturmak için işaretlerle çevrili json değeri olmalıdır , çünkü her şey JSONB türünde SQL için bir dizedir. Örneğin, boolean: 'true'dize: '"example"', tamsayı: '123'.
1valdis

22

Daha akıllı değil, daha basit:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';

13

Küçük bir varyasyon ama yeni bir şey değil. Gerçekten bir özelliği eksik ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.