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.