J'ai une table qui ressemble à ceci:
CREATE TABLE tracks (id SERIAL, artists JSON);
INSERT INTO tracks (id, artists)
VALUES (1, '[{"name": "blink-182"}]');
INSERT INTO tracks (id, artists)
VALUES (2, '[{"name": "The Dirty Heads"}, {"name": "Louis Richards"}]');
Plusieurs autres colonnes ne sont pas pertinentes pour cette question. Il y a une raison pour les stocker en tant que JSON.
Ce que j'essaie de faire, c'est de rechercher une piste qui a un nom de l'artiste (correspondance exacte) spécifique.
J'utilise cette requête:
SELECT * FROM tracks
WHERE 'ARTIST NAME' IN
(SELECT value->>'name' FROM json_array_elements(artists))
par exemple
SELECT * FROM tracks
WHERE 'The Dirty Heads' IN
(SELECT value->>'name' FROM json_array_elements(artists))
Cependant, cela effectue une analyse complète de la table et ce n'est pas très rapide. J'ai essayé de créer un index GIN en utilisant une fonction names_as_array(artists)
, et utilisé 'ARTIST NAME' = ANY names_as_array(artists)
, mais l'index n'est pas utilisé et la requête est considérablement plus lente.
jsonb
dans Postgres 9.4+Avec le nouveau type de données JSON binaire jsonb
, Postgres 9.4 introduit largement amélioré les options d’index . Vous pouvez maintenant avoir un index GIN sur un tableau jsonb
directement:
CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);
Pas besoin d'une fonction pour convertir le tableau. Cela prend en charge une requête:
SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
@>
étant le nouvel opérateur jsonb
"contient" , qui peut utiliser le GIN indice. (Pas pour le type json
, seulement jsonb
!)
ou vous utilisez la classe d'opérateurs GIN la plus spécialisée, autre que celle par défaut jsonb_path_ops
pour l'index:
CREATE INDEX tracks_artists_gin_idx ON tracks
USING gin (artists jsonb_path_ops);
Même requête.
Actuellement, jsonb_path_ops
Ne prend en charge que l'opérateur @>
. Mais c'est généralement beaucoup plus petit et plus rapide. Il y a plus d'options d'index, détails dans le manuel .
Si artists
ne contient que les noms indiqués dans l'exemple, il serait plus efficace de stocker une valeur JSON moins redondante pour commencer: juste le valeurs sous forme de texte primitives et le redondant clé peut être dans le nom de la colonne.
Notez la différence entre les objets JSON et les types primitifs:
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);
Question:
SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';
?
ne fonctionne pas pour l'objet valeurs, seulement keys et éléments de tableau.
Ou (plus efficace si les noms sont répétés souvent):
CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING gin (artistnames jsonb_path_ops);
Question:
SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;
json
dans Postgres 9.3+Cela devrait fonctionner avec une fonction IMMUTABLE
:
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)';
Créez cet index fonctionnel ) :
CREATE INDEX tracks_artists_gin_idx ON tracks
USING gin (json2arr(artists, 'name'));
Et utilisez une requête comme celle-ci. L'expression dans la clause WHERE
doit correspondre à celle de l'index:
SELECT * FROM tracks
WHERE '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));
Mis à jour avec des commentaires dans les commentaires. Nous devons utiliser opérateurs de tableaux pour prendre en charge l'index GIN.
Le "est contenu par" l'opérateur <@
dans ce cas.
Vous pouvez déclarer votre fonction IMMUTABLE
même si json_array_elements()
n'est pas n'était pas.
Auparavant, la plupart des fonctions JSON
n'étaient que STABLE
et non pas IMMUTABLE
. Il y a eu une discussion sur la liste des hackers pour changer cela. La plupart sont IMMUTABLE
maintenant. Vérifier avec:
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%';
Les index fonctionnels ne fonctionnent qu'avec les fonctions IMMUTABLE
.