it-swarm.com.de

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

Ich habe eine Tabelle, um Informationen über meine Kaninchen zu speichern. 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? Das habe ich mir ausgedacht:

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

Diese Abfrage gefällt mir 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?

77
Snowball

Ab PostgreSQL 9.4 können Sie das ? Operator :

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

Sie können sogar das ? Abfrage auf dem "food" Taste, wenn Sie zu 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-Kaninchenpfleger wahrscheinlich keine Zeit dafür.

Update: Hier ist eine Demonstration der Leistungsverbesserungen an einer Tabelle mit 1.000.000 Kaninchen, wobei jedes Kaninchen zwei Nahrungsmittel mag und 10% davon wie Karotten:

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
137
Snowball

Nicht schlauer, aber einfacher:

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

Sie können den Operator @> verwenden, um dies zu tun

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

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[]);
12
macias