PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Indice per trovare un elemento in un array JSON

jsonb in Postgres 9.4+

Il tipo di dati JSON binario jsonb migliora ampiamente le opzioni dell'indice. Ora puoi avere un indice GIN su un jsonb array direttamente:

CREATE TABLE tracks (id serial, artists jsonb);  -- !
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

Non c'è bisogno di una funzione per convertire l'array. Ciò supporterebbe una query:

SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';

@> essendo il jsonb operatore "contiene", che può utilizzare l'indice GIN. (Non per json , solo jsonb !)

Oppure usi la classe dell'operatore GIN più specializzata e non predefinita jsonb_path_ops per l'indice:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);  -- !

Stessa domanda.

Attualmente jsonb_path_ops supporta solo il @> operatore. Ma in genere è molto più piccolo e veloce. Ci sono più opzioni per l'indice, dettagli nel manuale .

Se la colonna artists contiene solo i nomi visualizzati nell'esempio, sarebbe più efficiente memorizzare solo i valori come testo JSON primitive e la chiave ridondante può essere il nome della colonna.

Nota la differenza tra oggetti JSON e tipi primitivi:

  • Utilizzo degli indici nell'array json in PostgreSQL
CREATE TABLE tracks (id serial, artistnames jsonb);
INSERT INTO tracks  VALUES (2, '["The Dirty Heads", "Louis Richards"]');

CREATE INDEX tracks_artistnames_gin_idx ON tracks USING gin (artistnames);

Domanda:

SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';

? non funziona per i valori degli oggetti , solo chiavi e elementi della matrice .

Oppure:

CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);

Domanda:

SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;

Più efficiente se i nomi sono altamente duplicati.

json in Postgres 9.3+

Questo dovrebbe funzionare con un IMMUTABLE funzione :

CREATE OR REPLACE FUNCTION json2arr(_j json, _key text)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT elem->>_key FROM json_array_elements(_j) elem)';

Crea questo indice funzionale :

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));

E usa una query come questo. L'espressione nel WHERE la clausola deve corrispondere a quella nell'indice:

SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));

Aggiornato con feedback nei commenti. Dobbiamo utilizzare operatori di array per supportare l'indice GIN.
L'operatore "è contenuto da" <@ in questo caso.

Note sulla volatilità delle funzioni

Puoi dichiarare la tua funzione IMMUTABLE anche se json_array_elements() non è non lo era.
La maggior parte dei JSON le funzioni erano solo STABLE , non IMMUTABLE . C'è stata una discussione sull'elenco degli hacker per cambiarlo. La maggior parte sono IMMUTABLE adesso. Verifica con:

SELECT p.proname, p.provolatile
FROM   pg_proc p
JOIN   pg_namespace n ON n.oid = p.pronamespace
WHERE  n.nspname = 'pg_catalog'
AND    p.proname ~~* '%json%';

Gli indici funzionali funzionano solo con IMMUTABLE funzioni.