Controlla se un array JSON di Postgres contiene una stringa


121

Ho una tabella per memorizzare le informazioni sui miei conigli. Assomiglia a questo:

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

Come dovrei trovare i conigli a cui piacciono le carote? Ho pensato a questo:

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

Non mi piace quella domanda. È un disastro.

Come guardiano di conigli a tempo pieno, non ho tempo per cambiare lo schema del mio database. Voglio solo nutrire adeguatamente i miei conigli. Esiste un modo più leggibile per eseguire tale query?


1
Domanda interessante. Ci ho giocato, ma poi mi sono reso conto, non sono sicuro di cosa intendi per "migliore". In base a quali criteri giudichi le tue risposte? Leggibilità? Efficienza? Altro?
David S

@ DavidS: (Ho aggiornato la domanda.) Preferirei la leggibilità all'efficienza. Certamente non mi aspetto niente di meglio di una scansione completa della tabella, dal momento che sto mantenendo lo schema fisso.
Palla di neve

11
È sbagliato che ho votato contro questa domanda a causa dei conigli?
osman

3
Ho appena votato questa domanda a causa dei conigli e poi ho visto il tuo commento @osman
1valdis

Ho visto il tuo commento e poi ho capito che devo votare a favore dei conigli
Peter Aron Zentai

Risposte:


186

A partire da PostgreSQL 9.4, puoi usare l' ?operatore :

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

Puoi persino indicizzare la ?query sulla "food"chiave se passi invece al 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';

Certo, probabilmente non hai tempo per quello come guardiano di conigli a tempo pieno.

Aggiornamento: ecco una dimostrazione dei miglioramenti delle prestazioni su un tavolo di 1.000.000 di conigli in cui a ogni coniglio piacciono due cibi e al 10% piacciono le carote:

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

come ottenere le righe in cui l'array di cibo all'interno di json non è vuoto, ad esempio se possiamo considerare, sono JSON, dove anche l'array di cibo è vuoto, puoi aiutarci
Bravo

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

1
Qualcuno sa come funziona nel caso in cui sia necessario selezionare un numero intero invece di una stringa / testo?
Rotareti

3
@Rotareti È possibile utilizzare la @> operatore : 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';. Nota che '2'è un numero JSON; non fatevi ingannare dalle virgolette.
Palla di neve

@ Palla di neve, questa query seleziona info - >> 'nome' da conigli dove (info -> 'cibo') :: jsonb? 'carote'; funziona perfettamente per la parola di ricerca da JSON. Ma come posso ottenere tutti i record non contiene la parola "carote"?
Milano

23

Potresti usare l'operatore @> per fare qualcosa di simile

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

1
Ciò è utile anche quando l'elemento è nullo
Lucio

2
Assicurati di prestare attenzione alle 'zecche che circondano le "carote" ... si rompe se le lasci fuori, anche se stai controllando un numero intero. (ho trascorso 3 ore cercando di trovare un numero intero, facendolo funzionare magicamente avvolgendo le 'zecche attorno al numero)
skplunkerin

@skplunkerin Dovrebbe essere un valore json circondato da segni di graduazione 'per formare una stringa, perché tutto è una stringa per SQL nel tipo JSONB. Ad esempio, booleano: 'true', stringa: '"example"', intero: '123'.
1valdis

22

Non più intelligente ma più semplice:

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

13

Una piccola variazione ma niente di nuovo infatti. Manca davvero una caratteristica ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.