2016-11-28 4 views
1

Ich bin ziemlich neu in Postgres und zur Zeit 9.6 verwenden. Beim Versuch, eine Volltextsuche in Postgres mit seinen Jsonb-Dokumenten zu implementieren, bemerkte ich langsame Suchergebnisse für verschachtelte Arrays. Ich habe den Befehl 'explain' verwendet und keine Indizes verwendet. Der Einfachheit halber Zweck habe ich eine Tabelle zu untersuchen:Index für verschachtelte Suche JSONB Array-Elemente in PostgreSQL

CREATE TABLE book (
    id BIGSERIAL NOT NULL, 
    data JSONB  NOT NULL 
); 

Meine verfügbaren Indizes:

CREATE INDEX book_author_idx 
    ON book USING GIN (to_tsvector('english', book.data ->> 'author')); 
CREATE INDEX book_author_name_idx 
    ON book USING GIN (to_tsvector('english', book.data -> 'author' ->> 'name')); 

Und einige Daten, ein Dokument zu füllen:

INSERT INTO book (data) 
VALUES (CAST('{"author": [{"id": 0, "name": "Cats"}, ' || 
      '   {"id": 1, "name": "Dogs"}]}' AS JSONB)); 

ich in der Lage bin zu suchen Bei Buchelementen, die die folgende Abfrage verwenden, wird jedoch kein Index verwendet. Mit meinen tatsächlichen Daten von 120k Produkten dauert es etwa 1200ms, während andere Suchen mit einem Index 0,2ms dauern.

EXPLAIN ANALYZE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book, jsonb_array_elements(data #> '{author}') author_array 
WHERE to_tsvector('english', author_array ->> 'name') @@ to_tsquery('cat'); 

Kontrast In der nächsten Abfrage verwendet die book_author_name_idx aber wegen der Arraystruktur nichts finden.

EXPLAIN ANALYZE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book 
WHERE to_tsvector('english', data -> 'author' ->> 'name') @@ to_tsquery('cat'); 

Wie kann ich meine Abfrage optimieren, um einen Sprachenindex zu verwenden? Ich bin mir bewusst, dass ich eine neue Tabelle für Autoren erstellen und nur die IDs referenzieren könnte, aber ich würde lieber alle Daten in einer Tabelle für die Leistung halten.

+1

Verwendung 'UNNEST()' und seine Freunde (Ergebnis-Satz Funktionen produzieren, wie 'jsonb_array_elements()') in einem 'LATERAL JOIN' verhindert die Verwendung von einem Index (zumindest auf Eigenschaften von ihnen berechnet). Wenn Sie bei dieser Struktur bleiben, müssen Sie eine benutzerdefinierte 'IMMUTABLE' -Funktion erstellen, um' Tsvector'-Werte aus Ihrer 'jsonb'-Spalte zu erzeugen und diese Funktion sowohl in Ihrem Index als auch in Ihrer Abfrage zu verwenden. – pozs

+0

Der interessante Teil darin ist die Tatsache, dass 'tsvector' keine eingebaute Aggregation hat, also müssen Sie entweder 1) Aggregate als Strings aggregieren (mit einer Grundregel) 2) eine benutzerdefinierte Aggregation für' tsvector' 3 erstellen) Verwenden Sie einen cleveren rekursiven CTE (weil für sie bereits eine Verkettung existiert). – pozs

Antwort

-1

mit den Spitzen von Posz comments fand ich eine Lösung. Weil das '||' Funktion funktioniert nicht so, wie ich es brauche, ich benutzte eine benutzerdefinierte Concat-Funktion für Tsvectors. Ich habe den Code von glittershark auf github verwendet und habe ihn von "Standard" in "Englisch" geändert, um meinen Bedürfnissen zu entsprechen.

CREATE OR REPLACE FUNCTION concat_tsvectors(tsv1 TSVECTOR, tsv2 TSVECTOR) 
    RETURNS TSVECTOR AS $$ 
BEGIN 
    RETURN coalesce(tsv1, to_tsvector('english', '')) 
     || coalesce(tsv2, to_tsvector('english', '')); 
END; 
$$ LANGUAGE plpgsql; 

CREATE AGGREGATE tsvector_agg (
BASETYPE = TSVECTOR, 
SFUNC = concat_tsvectors, 
STYPE = TSVECTOR, 
INITCOND = '' 
); 

Hier ist die benutzerdefinierte Funktion, die ich schrieb. Eingabe ist Daten wie JSONB und Ausgabe ist ein Tsvector mit aggregierten Autornamen.

CREATE OR REPLACE FUNCTION author_function(
    IN data  JSONB, 
    OUT resultNames TSVECTOR 
) 
    RETURNS TSVECTOR AS $$ 
DECLARE 
    authorRecords RECORD; 
    combinedAuthors JSONB []; 
    singleAuthor JSONB; 
BEGIN 
    FOR authorRecords IN (SELECT value 
         FROM jsonb_array_elements(data #> '{author}')) 
    LOOP 
    combinedAuthors := combinedAuthors || authorRecords.value; 
    END LOOP; 
    FOREACH singleAuthor IN ARRAY coalesce(combinedAuthors, '{}') 
    LOOP 
    resultNames := concat_tsvectors(resultNames, to_tsvector('english', singleAuthor ->> 'name')); 
    END LOOP; 
END; $$ 
LANGUAGE plpgsql 
IMMUTABLE; 

Dann habe ich einen Index für meine Buchobjekte eingerichtet.

CREATE INDEX book_author_function_idx 
    ON book USING GIN (author_function(book.data)); 

Die Autorennamen ging bereits durch die to_tsvector (‚Englisch‘, singleAuthor) Funktion, so kann ich für sie wie folgt abfragen:

EXPLAIN ANALYSE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book 
WHERE author_function(book.data) @@ to_tsquery('cat'); 

Als Ergebnis Abfragen für meine aktuellen Daten ging von 1100-1200 ms bis ~ 0,5 ms. Ich bin mir nicht sicher, ob dies die beste Lösung ist. Wenn Sie also bessere Vorschläge haben, lassen Sie es mich bitte wissen.

Verwandte Themen