Überprüfen Sie, ob ein Postgres-JSON-Array eine Zeichenfolge enthält

119

Ich habe einen Tisch, an dem ich Informationen über meine Kaninchen speichern kann. Es sieht aus wie das:

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

Wie soll ich die Kaninchen finden, die Karotten mögen? Ich habe mir das ausgedacht:

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

Ich mag diese Abfrage nicht. Es ist ein Chaos.

Als Vollzeit-Kaninchenhalter habe ich keine Zeit, mein Datenbankschema zu ändern. Ich möchte nur meine Kaninchen richtig füttern. Gibt es eine besser lesbare Möglichkeit, diese Abfrage durchzuführen?

Schneeball
quelle
1
Interessante Frage. Ich habe damit herumgespielt, aber dann wurde mir klar, ich bin mir nicht sicher, was du mit "besser" meinst. Nach welchen Kriterien beurteilen Sie Ihre Antworten? Lesbarkeit? Effizienz? Andere?
David S
@ DavidS: (Ich habe die Frage aktualisiert.) Ich würde die Lesbarkeit der Effizienz vorziehen. Ich erwarte auf keinen Fall etwas Besseres als einen vollständigen Tabellenscan, da ich das Schema festhalte.
Schneeball
11
Ist es falsch, dass ich diese Frage wegen der Kaninchen positiv bewertet habe?
Osman
3
Ich habe diese Frage gerade wegen Kaninchen positiv
bewertet
Ich habe Ihren Kommentar gesehen und dann festgestellt, dass ich wegen Kaninchen abstimmen muss
Peter Aron Zentai

Antworten:

183

Ab PostgreSQL 9.4 können Sie den ?Operator verwenden :

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

Sie können die ?Abfrage sogar für den "food"Schlüssel indizieren, wenn Sie stattdessen zum Typ jsonb wechseln :

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

Natürlich haben Sie als Vollzeit-Kaninchenhalter wahrscheinlich keine Zeit dafür.

Update: Hier ist eine Demonstration der Leistungsverbesserungen an einem Tisch mit 1.000.000 Kaninchen, an dem jedes Kaninchen zwei Lebensmittel mag und 10% davon Karotten mögen:

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
Schneeball
quelle
Wie bekomme ich die Zeilen, in denen das Food-Array in JSON nicht leer ist
Bravo
1
@ Bravoselect * from rabbits where info->'food' != '[]';
Schneeball
1
Weiß jemand, wie dies funktioniert, wenn Sie eine Ganzzahl anstelle einer Zeichenfolge / eines Textes auswählen müssen?
Rotareti
3
@Rotareti Sie können den Operator @> verwenden : 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';. Beachten Sie, dass dies '2'eine JSON-Nummer ist. Lassen Sie sich nicht von den Zitaten irreführen.
Schneeball
@Snowball, diese Abfrage wählen Sie info - >> 'Name' von Kaninchen wo (info -> 'Nahrung') :: jsonb? 'Möhren'; funktioniert perfekt für Suchwörter von JSON. Aber wie ich alle Aufzeichnungen bekommen kann, enthält nicht "Karotten" Wort?
Mailand
23

Sie könnten den Operator @> verwenden, um so etwas zu tun

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
gori
quelle
1
Dies ist nützlich, wenn der Artikel ebenfalls null ist
Lucio
2
Achten Sie auf die 'Zecken um "Karotten" ... es bricht, wenn Sie diese weglassen, auch wenn Sie nach einer ganzen Zahl suchen. (verbrachte 3 Stunden damit, eine ganze Zahl zu finden, damit sie auf magische Weise funktioniert, indem 'Zecken um die Zahl
gewickelt werden
@skplunkerin Es sollte ein JSON-Wert sein, der von Häkchen umgeben ist ', um eine Zeichenfolge zu bilden, da alles eine Zeichenfolge für SQL im JSONB-Typ ist. Zum Beispiel boolean : 'true', string : '"example"', integer : '123'.
1valdis
22

Nicht schlauer, aber einfacher:

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

Eine kleine Variation, aber nichts Neues. Es fehlt wirklich eine Funktion ...

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