Перевірте, чи містить масив Postgres JSON рядок


121

У мене є таблиця для зберігання інформації про моїх кроликів. Це виглядає приблизно так:

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: (Я оновив питання.) Я вважаю за краще читабельність над ефективністю. Я, звичайно, не очікую нічого кращого, ніж повне сканування таблиці, оскільки я тримаю схему виправлену.
Сніжок

11
Чи не так, що я відмовився від цього питання через кроликів?
осман

3
Я просто підтримав це питання через кроликів, а потім побачив ваш коментар @osman
1valdis

Я побачив ваш коментар, а потім зрозумів, що мені потрібно подати заяву через кроликів
Пітер Арон Зентай

Відповіді:


187

Станом на 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';

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

Оновлення: Ось демонстрація поліпшення продуктивності на столі з 1 000 000 кроликів, де кожному кроликові подобається два продукти, а 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

як дістати рядки, де масив їжі всередині json не порожній, наприклад, якщо ми можемо вважати, це JSON, де масив їжі також порожній, чи можете ви допомогти
Bravo

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

1
Хтось знає, як це працює, якщо вам потрібно вибрати ціле число замість рядка / тексту?
Ротарети

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, цей запит виберіть інформацію - >> 'ім'я' від кроликів, де (інформація -> 'їжа') :: jsonb? 'морква'; працює ідеально для пошуку слова від JSON. Але як я можу отримати всі записи, що не містять слова «морква»?
Мілан

23

Ви можете скористатися оператором @>, щоб зробити щось подібне

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

1
Це корисно і тоді, коли предмет є недійсним
Lucio

2
Не забудьте звернути увагу на 'кліщів, що оточують "моркву" ... вона зламається, якщо ви їх залишите, навіть якщо ви перевіряєте ціле число. (витратив 3 години, намагаючись знайти ціле число, магічно працюючи, '
обмотуючи

@skplunkerin 'Для формування рядка має бути значення json, оточене тиками, тому що все є рядком для SQL типу JSONB. Наприклад, логічне значення: 'true', рядок: '"example"', ціле число: '123'.
1валдіс,

22

Не розумніший, але простіший:

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

13

Невелика варіація, але нічого нового не стосується. У ньому дійсно відсутня функція ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.