Проверьте, содержит ли массив Postgres JSON строку

122

У меня есть таблица для хранения информации о моих кроликах. Выглядит это так:

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

Как найти кроликов, которые любят морковь? Я придумал это:

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

Мне не нравится этот запрос. Это беспорядок.

У меня, как у кроликовода, занятого полный рабочий день, нет времени менять схему базы данных. Я просто хочу как следует кормить своих кроликов. Есть ли более понятный способ выполнить этот запрос?

Снежный шар
источник
1
Интересный вопрос. Я играл с этим, но потом меня осенило: я не уверен, что вы имеете в виду под словом «лучше». По каким критериям вы судите свои ответы? Читаемость? Эффективность? Другой?
David S
@DavidS: (Я обновил вопрос). Я предпочел бы удобочитаемость эффективности. Я, конечно, не ожидаю ничего лучше, чем полное сканирование таблицы, так как я придерживаюсь фиксированной схемы.
Snowball
12
Это неправильно, что я проголосовал за этот вопрос из-за кроликов?
osman
3
Я только что проголосовал за этот вопрос из-за кроликов, а затем увидел ваш комментарий @osman
1valdis
Я видел ваш комментарий, а затем понял, что мне нужно проголосовать из-за кроликов
Питер Арон Зентаи

Ответы:

188

Начиная с PostgreSQL 9.4, вы можете использовать ?оператор :

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

Вы даже можете проиндексировать ?запрос по "food"ключу, если вместо этого переключитесь на тип 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';

Конечно, у вас, как у кроликовода, занятого полный рабочий день, на это нет времени.

Обновление: вот демонстрация улучшения производительности на столе из 1000000 кроликов, где каждый кролик любит два продукта, а 10% из них любят морковь:

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
Снежный шар
источник
как получить строки, в которых массив food внутри json не пуст, например, если мы можем рассмотреть, это JSON, где массив food также пуст, вы можете помочь?
Браво,
1
@Bravoselect * from rabbits where info->'food' != '[]';
Snowball
1
Кто-нибудь знает, как это работает, если вам нужно выбрать целое число вместо строки / текста?
Rotareti
3
@Rotareti Вы можете использовать @> оператор : 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';. Обратите внимание, что '2'это номер JSON; не вводите в заблуждение цитаты.
Snowball
@Snowball, этот запрос выбирает информацию - >> 'name' from rabbits where (info -> 'food') :: jsonb? 'морковь'; отлично работает для поискового слова из JSON. Но как я могу получить все записи, не содержащие слова «морковь»?
Милан
23

Вы можете использовать оператор @>, чтобы сделать что-то вроде

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
гори
источник
1
Это полезно , когда элемент является недействительным , а также
Лусио
2
Удостоверьтесь, что вы обратили внимание на 'галочки, окружающие «морковь» ... он сломается, если вы не укажете их, даже если вы проверяете целое число. (потратил 3 часа, пытаясь найти целое число, '
заставляя
@skplunkerin 'Для формирования строки должно быть значение json, окруженное галочками , потому что все является строкой для SQL в типе JSONB. Например, логическое значение: 'true', строка: '"example"', целое число: '123'.
1valdis
22

Не умнее, а проще:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';
chrmod
источник
13

Небольшая вариация, но ничего нового. Здесь действительно отсутствует функция ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
Масиаса
источник