Ich habe einen Tisch, der 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 Titel nachzuschlagen, der einen bestimmten Künstlernamen hat (genaue Übereinstimmung).
Ich benutze diese Abfrage:
SELECT * FROM tracks
WHERE 'ARTIST NAME' IN
(SELECT value->>'name' FROM json_array_elements(artists))
zum Beispiel
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 zu erstellen names_as_array(artists)
, und verwendet 'ARTIST NAME' = ANY names_as_array(artists)
, aber der Index wird nicht verwendet und die Abfrage ist tatsächlich erheblich langsamer.
Antworten:
jsonb
in Postgres 9.4+Mit dem neuen binären JSON-Datentyp
jsonb
führte Postgres 9.4 stark verbesserte Indexoptionen ein . Sie können jetztjsonb
direkt einen GIN-Index für ein Array erstellen:CREATE TABLE tracks (id serial, artists jsonb); CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);
Zum Konvertieren des Arrays ist keine Funktion erforderlich. Dies würde eine Abfrage unterstützen:
SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
@>
Dies ist der neuejsonb
Operator "enthält" , der den GIN-Index verwenden kann. (Nicht für den Typjson
, nurjsonb
!)Oder Sie verwenden die speziellere, nicht standardmäßige GIN-Operatorklasse
jsonb_path_ops
für den Index:CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists jsonb_path_ops);
Gleiche Abfrage.
jsonb_path_ops
Unterstützt derzeit nur den@>
Operator. Aber es ist normalerweise viel kleiner und schneller. Es gibt weitere Indexoptionen, Details im Handbuch .Wenn
artists
nur Namen enthalten sind, wie im Beispiel gezeigt, wäre es effizienter, zunächst einen weniger redundanten JSON-Wert zu speichern: Nur die Werte als Textprimitive und der redundante Schlüssel können im Spaltennamen enthalten sein.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 die Objektwerte , nur Schlüssel und Array - Elemente .Oder (effizienter, wenn Namen häufig 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 Funktionsindex :
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (json2arr(artists, 'name'));
Und verwenden Sie eine solche Abfrage . 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 von" ist
<@
in diesem Fall.Hinweise zur Funktionsvolatilität
Sie können Ihre Funktion
IMMUTABLE
auch dann deklarieren , wenn diesjson_array_elements()
nichtder Fall ist.Die meisten
JSON
Funktionen waren früher nurSTABLE
, nichtIMMUTABLE
. Es gab eine Diskussion auf der Hackerliste, um das zu ändern. Die meisten sindIMMUTABLE
jetzt. Überprüfen Sie mit: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.quelle
SETOF
nicht in einem Index verwendet werden kann. Wenn ich das entferne, kann ich den Index erstellen, er wird jedoch vom Abfrageplaner nicht verwendet. Auch sind sowohl json_array_elements als auch array_aggIMMUTABLE
SET enable_seqscan = off;
(nur zu Debugging-Zwecken) stackoverflow.com/questions/14554302/… .