8

Ich habe ziemlich komplexe JSONB in ​​einer jsonb Spalte gespeichert.So implementieren Sie die Volltextsuche für komplexe verschachtelte JSONB in ​​Postgresql

DB-Tabelle wie folgt aussieht:

CREATE TABLE sites (
    id text NOT NULL, 
    doc jsonb, 
    PRIMARY KEY (id) 
) 

Daten, die wir in doc Spalt Speicherung ist ein komplexes verschachtelten JSONB Daten:

{ 
     "_id": "123", 
     "type": "Site", 
     "identification": "Custom ID", 
     "title": "SITE 1", 
     "address": "UK, London, Mr Tom's street, 2", 
     "buildings": [ 
      { 
       "uuid": "12312", 
       "identification": "Custom ID", 
       "name": "BUILDING 1", 
       "deposits": [ 
        { 
         "uuid": "12312", 
         "identification": "Custom ID",    
         "audits": [ 
          { 
          "uuid": "12312",   
           "sample_id": "SAMPLE ID"     
          } 
         ] 
        } 
       ] 
      } 
     ] 
    } 

So Struktur meines JSONB wie folgt aussieht:

SITE 
    -> ARRAY OF BUILDINGS 
    -> ARRAY OF DEPOSITS 
     -> ARRAY OF AUDITS 

Wir müssen die Volltextsuche so implementieren me Werte in jeder der Art des Eintrags:

SITE (identification, title, address) 
BUILDING (identification, name) 
DEPOSIT (identification) 
AUDIT (sample_id) 

SQL-Abfrage sollte eine Volltextsuche nur in diesen Feldwerten ausführen.

Ich denke, müssen GIN Indizes und so etwas wie tsvector verwenden, aber nicht genug Postgresql Hintergrund haben.

Also, meine Frage ist es möglich, solche verschachtelten Strukturen JSONB zu indexieren und dann abzufragen?

+1

Der erste Schuss wäre "denormalize" den Speicher: opfern einige Speicherstelle für die Kürze. Extrahieren Sie benötigte Daten in separaten Feldern: Site, Gebäude, Depot, Audit, die reine String-Kontantenation der benötigten Felder enthalten, zB 'building.identification || ';' || building.title || ';'|| building.address' etc . (Dies kann mit den Funktionen von Postgres als Standardwerte oder Trigger-basiert geschehen, wenn Ihre Daten geändert werden). Dann erstellen Sie GIN-Indizes für diese Felder -> und konstruieren Sie dann Ihre entsprechenden Volltext-Abfragen für diese Felder –

+0

Danke @IlyaDyoshin. Ich mag deine Idee - werde versuchen, damit zu experimentieren. – rusllonrails

+0

oder Sie können bis 10.0 Release warten - wo JSON/JSONB FTS wird erster Klasse Bürger sein https://www.postgresql.org/docs/10/static/release-10.html –

Antwort

3

wir neue Spalte von tsvector Typ hinzufügen:

alter table sites add column tsvector tsvector; 

Lassen Sie uns nun einen Trigger erstellen, die Lexeme sammeln, organisieren sie und unsere tsvector setzen. Wir werden 4 Gruppen (A, B, C, D) verwenden - dies ist eine spezielle Tsvector-Funktion, mit der Sie Lexems später bei der Suche unterscheiden können (siehe Beispiele in Handbuch https://www.postgresql.org/docs/current/static/textsearch-controls.html; leider unterstützt diese Funktion nur bis zu 4 Gruppen becase für die Entwickler nur 2 Bits reserviert, aber wir sind hier glücklich, brauchen wir nur 4 Gruppen):

create or replace function t_sites_tsvector() returns trigger as $$ 
declare 
    dic regconfig; 
    part_a text; 
    part_b text; 
    part_c text; 
    part_d text; 
begin 
    dic := 'simple'; -- change if you need more advanced word processing (stemming, etc) 

    part_a := coalesce(new.doc->>'identification', '') || ' ' || coalesce(new.doc->>'title', '') || ' ' || coalesce(new.doc->>'address', ''); 

    select into part_b string_agg(coalesce(a, ''), ' ') || ' ' || string_agg(coalesce(b, ''), ' ') 
    from (
    select 
     jsonb_array_elements((new.doc->'buildings'))->>'identification', 
     jsonb_array_elements((new.doc->'buildings'))->>'name' 
) _(a, b); 

    select into part_c string_agg(coalesce(c, ''), ' ') 
    from (
    select jsonb_array_elements(b)->>'identification' from (
     select jsonb_array_elements((new.doc->'buildings'))->'deposits' 
    ) _(b) 
) __(c); 

    select into part_d string_agg(coalesce(d, ''), ' ') 
    from (
    select jsonb_array_elements(c)->>'sample_id' 
    from (
     select jsonb_array_elements(b)->'audits' from (
     select jsonb_array_elements((new.doc->'buildings'))->'deposits' 
    ) _(b) 
    ) __(c) 
) ___(d); 

    new.tsvector := setweight(to_tsvector(dic, part_a), 'A') 
    || setweight(to_tsvector(dic, part_b), 'B') 
    || setweight(to_tsvector(dic, part_c), 'C') 
    || setweight(to_tsvector(dic, part_d), 'D') 
    ; 
    return new; 
end; 
$$ language plpgsql immutable; 

create trigger t_sites_tsvector 
    before insert or update on sites for each row execute procedure t_sites_tsvector(); 

^^ - navigieren Sie dieser Ausschnitt ist größer, als es aussieht (vor allem von Ihnen MacOS haben w/o Bildlaufleisten ...)

lassen Sie uns jetzt GIN Index erstellen Suchanfragen Speedup (macht Sinn, wenn Sie viele Zeilen haben - sagen wir, mehr als hundert oder tausend):

create index i_sites_fulltext on sites using gin(tsvector); 

Und jetzt legen wir etwas zu überprüfen:

insert into sites select 1, '{ 
     "_id": "123", 
     "type": "Site", 
     "identification": "Custom ID", 
     "title": "SITE 1", 
     "address": "UK, London, Mr Tom''s street, 2", 
     "buildings": [ 
      { 
       "uuid": "12312", 
       "identification": "Custom ID", 
       "name": "BUILDING 1", 
       "deposits": [ 
        { 
         "uuid": "12312", 
         "identification": "Custom ID", 
         "audits": [ 
          { 
          "uuid": "12312", 
           "sample_id": "SAMPLE ID" 
          } 
         ] 
        } 
       ] 
      } 
     ] 
    }'::jsonb; 

prüfen mit select * from sites; - Sie müssen sehen, dass tsvector Spalte mit einigen Daten gefüllt ist.

machen wir es jetzt fragen:

select * from sites where tsvector @@ to_tsquery('simple', 'sample'); 

- es ist unser Rekord zurückkehren. In diesem Fall suchen wir nach 'sample' Wort und es ist uns egal in welcher Gruppe es gefunden wird.

Sagen wir es ändern und versuchen, nur in der Gruppe A („SITE (Identifizierung, Titel, Adresse)“, wie Sie es beschrieben) suchen:

select * from sites where tsvector @@ to_tsquery('simple', 'sample:A'); 

- das muss nichts zurück, weil Wort 'sample' nur sitzt in Gruppe D ("AUDIT (Beispiel_ID)"). In der Tat:

select * from sites where tsvector @@ to_tsquery('simple', 'sample:D'); 

- werden uns wieder unseren Rekord zurückgeben.

Beachten Sie, dass Sie to_tsquery(..), nicht plainto_tsquery(..) verwenden müssen, um 4 Gruppen zu adressieren. Sie müssen also Ihre Eingaben selbst bereinigen (vermeiden Sie die Verwendung oder das Entfernen von Sonderzeichen wie & und |, da diese in tsquery Werte eine besondere Bedeutung haben).

Und die gute Nachricht ist, dass man verschiedene Gruppen in einer einzigen Abfrage, wie folgt kombinieren:

ist mit
select * from sites where tsvector @@ to_tsquery('simple', 'sample:D & london:A'); 

Dem anderen Weg zu gehen (zB wenn Sie mit mehr als 4 Gruppen arbeiten) mehrere tsvectors, die jeweils in einer separaten Spalte sitzen, erstellen sie mit einer einzelnen Abfrage, erstellen einen Index (Sie können einen einzelnen Index für mehrere tsvector Spalten erstellen) und die Adressierung separater Spalten abfragen. Es ist ähnlich dem, was ich oben erklärt habe, aber vielleicht weniger effizient.

Hoffe, das hilft.

+0

Vielen Dank @Nick. Werft schon bald einen Blick auf Ihren Vorschlag werfen. – rusllonrails

+1

Sicher. Lassen Sie mich wissen, ob etwas unklar ist. – Nick

+1

Hey @Nick, ich habe vergessen, dir großen Dank zu sagen) Ich habe dich getestet und es funktioniert hervorragend! Danke dir so viel Freund – rusllonrails

Verwandte Themen