Sprawdź, czy tablica Postgres JSON zawiera ciąg

121

Mam tabelę do przechowywania informacji o moich królikach. To wygląda tak:

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

Jak znaleźć króliki, które lubią marchewki? Wymyśliłem to:

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

Nie podoba mi się to zapytanie. To bałagan.

Jako pełnoetatowy opiekun królików nie mam czasu na zmianę schematu bazy danych. Chcę tylko odpowiednio karmić moje króliki. Czy istnieje bardziej czytelny sposób wykonania tego zapytania?

Śnieżna kula
źródło
1
Interesujące pytanie. Bawiłem się tym, ale wtedy dotarło do mnie, że nie jestem pewien, co masz na myśli mówiąc „lepiej”. Według jakich kryteriów oceniasz swoje odpowiedzi? Czytelność? Wydajność? Inny?
David S
@DavidS: (Zaktualizowałem pytanie.) Wolałbym czytelność od wydajności. Z pewnością nie oczekuję niczego lepszego niż pełne skanowanie tabeli, ponieważ utrzymuję schemat naprawiony.
Snowball
11
Czy to źle, że zagłosowałem za tym pytaniem z powodu królików?
osman
3
Właśnie upvoted to pytanie, ponieważ królików i wtedy zobaczył swój komentarz @osman
1valdis
Widziałem twój komentarz, a potem zdałem sobie sprawę, że muszę głosować za królikami
Peter Aron Zentai

Odpowiedzi:

187

Począwszy od PostgreSQL 9.4, możesz użyć ?operatora :

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

Możesz nawet zindeksować ?zapytanie w "food"kluczu, jeśli zamiast tego przełączysz się na typ jsonb :

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

Oczywiście jako pełnoetatowy opiekun królików prawdopodobnie nie masz na to czasu.

Aktualizacja: Oto demonstracja poprawy wydajności na stole 1000000 królików, gdzie każdy królik lubi dwa pokarmy, a 10% z nich lubi marchewki:

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
Śnieżna kula
źródło
jak uzyskać wiersze, w których tablica food wewnątrz json nie jest pusta, na przykład, jeśli możemy wziąć pod uwagę, są to JSON, gdzie tablica żywności również jest pusta, czy możesz pomóc
Bravo
1
@Bravoselect * from rabbits where info->'food' != '[]';
Snowball
1
Czy ktoś wie, jak to działa w przypadku, gdy trzeba wybrać liczbę całkowitą zamiast ciągu / tekstu?
Rotareti
3
@Rotareti Można użyć @> DOP : 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';. Zauważ, że '2'jest to numer JSON; nie daj się zwieść cytatom.
Snowball
@Snowball, to zapytanie wybiera info - >> 'name' z królików gdzie (info -> 'food') :: jsonb? 'marchew'; działa idealnie dla wyszukiwanego słowa z formatu JSON. Ale jak mogę uzyskać wszystkie rekordy, które nie zawierają słowa „marchewki”?
Mediolan
23

Możesz użyć operatora @>, aby zrobić to na przykład

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
gori
źródło
1
Jest to przydatne, gdy pozycja jest również pusta
Lucio,
2
Upewnij się, że zwracasz uwagę na 'tiki otaczające „marchewki” ... pęknie, jeśli je pominiesz, nawet jeśli sprawdzasz liczbę całkowitą. (spędził 3 godziny próbując znaleźć liczbę całkowitą, magicznie działając poprzez owijanie 'tików wokół liczby)
skplunkerin
@skplunkerin Powinna być wartością json otoczoną 'znacznikami, aby utworzyć łańcuch, ponieważ wszystko jest ciągiem dla SQL w typie JSONB. Na przykład, wartość logiczna: 'true', string: '"example"', całkowita: '123'.
1valdis
22

Nie mądrzejszy, ale prostszy:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';
chrmod
źródło
13

Mała zmiana, ale nic nowego w rzeczywistości. Naprawdę brakuje funkcji ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
macias
źródło