it-swarm.com.de

Index zum Suchen eines Elements in einem JSON-Array

Ich habe eine Tabelle, die so aussieht:

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

Es gibt mehrere andere Spalten, die für diese Frage nicht relevant sind. Es gibt einen Grund, sie als JSON zu speichern.

Ich versuche, einen Track zu suchen, der ein bestimmtes Künstlername (genaue Übereinstimmung) hat.

Ich benutze diese Abfrage:

SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))

beispielsweise

SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))

Dies führt jedoch einen vollständigen Tabellenscan durch und ist nicht sehr schnell. Ich habe versucht, einen GIN-Index mit einer Funktion names_as_array(artists) zu erstellen und habe 'ARTIST NAME' = ANY names_as_array(artists) verwendet. Der Index wird jedoch nicht verwendet und die Abfrage ist tatsächlich erheblich langsamer.

74
JeffS

jsonb in Postgres 9.4+

Mit dem neuen binären JSON-Datentyp jsonb wurde Postgres 9.4 eingeführt stark verbesserte Indexoptionen . Sie können jetzt einen GIN-Index für ein jsonb -Array direkt haben:

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

Keine Funktion zum Konvertieren des Arrays erforderlich. Dies würde eine Abfrage unterstützen:

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

@> ist der neue Operator jsonb "enthält" , der die GIN verwenden kann Index. (Nicht für Typ json, nur jsonb!)

Oder Sie verwenden die spezialisiertere, nicht standardmäßige GIN-Operator-Klasse jsonb_path_ops für den Index:

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

Gleiche Abfrage.

Derzeit unterstützt jsonb_path_ops Nur den Operator @>. Aber es ist in der Regel viel kleiner und schneller. Es gibt mehr Indexoptionen, Details im Handbuch .


Wenn artists nur die im Beispiel angezeigten Namen enthält, ist es effizienter, zunächst einen weniger redundanten JSON-Wert zu speichern: nur die Werte als Text Primitive und den redundanten Schlüssel kann im Spaltennamen stehen.

Beachten Sie den Unterschied zwischen JSON-Objekten und primitiven Typen:

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);

Abfrage:

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

? funktioniert nicht für Objekt Werte, nur keys und array elements.
Oder (effizienter, wenn Namen oft wiederholt werden):

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

Abfrage:

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

json in Postgres 9.3+

Dies sollte mit einer IMMUTABLE -Funktion funktionieren:

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)';

Erstellen Sie diesen funktionalen Index :

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

Und verwenden Sie eine Abfrage wie folgt. Der Ausdruck in der WHERE -Klausel muss mit dem im Index übereinstimmen:

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

Aktualisiert mit Feedback in Kommentaren. Wir müssen Array-Operatoren verwenden, um den GIN-Index zu unterstützen.
Der Operator "ist enthalten in" <@ in diesem Fall.

Hinweise zur Funktionsvolatilität

Sie können Ihre Funktion IMMUTABLE deklarieren, auch wenn json_array_elements() ist nicht war nicht.
Die meisten JSON Funktionen waren bisher nur STABLE, nicht IMMUTABLE. Es gab eine Diskussion auf der Hackerliste, um das zu ändern. Die meisten sind jetzt IMMUTABLE. Erkundigen Sie sich bei:

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%';

Funktionsindizes funktionieren nur mit IMMUTABLE Funktionen.

119