Verifique se um array JSON Postgres contém uma string

121

Eu tenho uma mesa para armazenar informações sobre meus coelhos. Se parece com isso:

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

Como devo encontrar os coelhos que gostam de cenouras? Eu vim com isso:

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

Eu não gosto dessa consulta. É uma bagunça.

Como tratador de coelhos em tempo integral, não tenho tempo para alterar meu esquema de banco de dados. Eu só quero alimentar meus coelhos adequadamente. Existe uma maneira mais legível de fazer essa consulta?

Bola de neve
fonte
1
Pergunta interessante. Eu brinquei com isso, mas então me dei conta, não tenho certeza do que você quer dizer com "melhor". Por quais critérios você está julgando suas respostas? Legibilidade? Eficiência? De outros?
David S
@DavidS: (Eu atualizei a pergunta.) Prefiro a legibilidade à eficiência. Certamente não espero nada melhor do que uma varredura completa da tabela, já que estou mantendo o esquema fixo.
Snowball
11
É errado eu ter votado a favor desta questão por causa dos coelhos?
osman
3
Acabei de votar esta questão por causa dos coelhos e então vi seu comentário @osman
1valdis
Eu vi seu comentário e então percebi que preciso votar por causa dos coelhos
Peter Aron Zentai

Respostas:

187

A partir do PostgreSQL 9.4, você pode usar o ?operador :

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

Você pode até indexar a ?consulta na "food"chave se alternar para o tipo 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';

Claro, você provavelmente não tem tempo para isso como um criador de coelhos em tempo integral.

Atualização: aqui está uma demonstração das melhorias de desempenho em uma mesa de 1.000.000 de coelhos, onde cada coelho gosta de dois alimentos e 10% deles gosta de cenouras:

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
Bola de neve
fonte
como obter as linhas onde a matriz alimentar dentro de json não está vazia, por exemplo, se pudermos considerar, eles são JSON, onde a matriz alimentar também está vazia, você pode ajudar
Bravo
1
@Bravoselect * from rabbits where info->'food' != '[]';
Snowball de
1
Alguém sabe como isso funciona caso você precise selecionar um inteiro em vez de uma string / texto?
Rotareti
3
@Rotareti Você pode usar o @> operador : 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';. Observe que '2'é um número JSON; não se deixe enganar pelas aspas.
Snowball
@ Bola de neve, esta consulta seleciona informações - >> 'nome' dos coelhos onde (informações -> 'comida') :: jsonb? 'cenouras'; está funcionando perfeitamente para palavra de pesquisa de JSON. Mas como posso obter todos os registros que não contenham a palavra 'cenouras'?
Milão
23

Você poderia usar o operador @> para fazer algo como

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
gori
fonte
1
Isso é útil quando o item também é nulo
Lucio
2
Certifique-se de prestar atenção aos 'carrapatos em torno de "cenouras" ... ele quebra se você deixá-los de fora, mesmo se estiver verificando um inteiro. (passou 3 horas tentando encontrar um número inteiro, fazendo-o funcionar magicamente envolvendo 'tiques em torno do número)
skplunker em
@skplunkerin Deve ser o valor json entre marcas 'para formar uma string, porque tudo é uma string para SQL no tipo JSONB. Por exemplo, boolean: 'true', string: '"example"', inteiro: '123'.
1valdis
22

Não mais inteligente, mas mais simples:

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

Uma pequena variação, mas nada de fato novo. Está realmente faltando um recurso ...

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