8

かなり複雑なJSONBが1つのjsonbカラムに格納されています。PostgreSQLの複雑なネストされたJSONBで全文検索を実装する方法

DBテーブルのようになります。私たちはdoc列に保管されている

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

データは、複雑な入れ子になったJSONBデータです:

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

{ 
     "_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の構造は次のようになります

これで全文検索を実装する必要があります私は各エントリの種類の値を入力します。

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

SQLクエリでは、これらのフィールド値でのみ全文検索を実行する必要があります。

GINインデックスなどを使用し、tsvectorのようなものが必要ですが、十分なPostgresqlバックグラウンドがないと思います。

私の疑問は、そのようなネストされたJSONB構造体のインデックスを作成してクエリすることです。

+1

最初のショットはストレージを「非正規化」します。簡潔にするために保存場所を犠牲にします。必要なフィールドの純粋な文字列の連結を含む、サイト、ビルディング、デポジット、監査、別々のフィールドで必要なデータを抽出する: 'building.identification || ';' || building.title || ';'|| building.address'これはpostgresの関数をデフォルト値として使用するか、データが変更された場合はトリガベースで行うことができます。これらのフィールドにGINインデックスを作成してから、それらのフィールドに対応するフルテキストクエリを作成してください。 –

+0

ありがとう@IlyaDyoshin。私はあなたの考えが好きです - それを実験しようとします。 – rusllonrails

+0

または10.0リリースまで待つことができます - json/jsonb FTSはファーストクラスの市民になりますhttps://www.postgresql.org/docs/10/static/release-10.html –

答えて

3

はのはtsvectorタイプの新しい列を追加してみましょう:

alter table sites add column tsvector tsvector; 

は、今度は、lexemsを収集し、それらを整理し、私たちのtsvectorに置くトリガーを作成してみましょう。私たちは4つのグループ(A、B、C、D)を使用します - これは後で検索時にレクセルを区別できる特別なtsvectorの機能です(マニュアルhttps://www.postgresql.org/docs/current/static/textsearch-controls.htmlの例を参照してください;残念ながらこの機能は最大4つのグループのみをサポートします)開発者)は、そのための唯一の2ビットを予約し、私たちはここにラッキーです、我々は唯一の4つのグループが必要になります。

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

^^ - このスニペットは、それがのw/MacOSのを持っている(特にあなたの見た目よりも大きいですが、それをスクロールします

:)数百または数千以上、言う - Oスクロールバー...)

今(あなたは多くの行を持っている場合は理にかなっているのは、検索クエリを高速化するためにGINインデックスを作成してみましょう

create index i_sites_fulltext on sites using gin(tsvector); 

そして今、私たちは何かを確認するために挿入します。

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; 

チェックselect * from sites;で - あなたはtsvector列は、いくつかのデータで満たされていることを確認しなければなりません。

今度はそれを照会してみましょう:

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

- それが私たちのレコードを返す必要があります。この場合、'sample'という単語が検索され、検索されるグループはわかりません。

のは、それを変更し、グループのみA(「SITE(識別、タイトル、アドレス)」あなたはそれを説明するように)で検索してみましょう:

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

- 単語'sample'だけで座っているので、これは何も返さない必要がありますグループD(「AUDIT(sample_id)」)。確かに:

​​

- 私たちの記録は再び私たちに返されます。

4グループをアドレス指定できるようにするには、to_tsquery(..)ではなく、plainto_tsquery(..)を使用する必要があることに注意してください。したがって、入力内容を自分で消毒する必要があります(&|のような特殊文字を使用または削除しないでください。特別な意味はtsqueryの値なので)。

そして、良いニュースは、あなたがこのように、単一のクエリで異なるグループを組み合わせることができるということです。(あなたが4つの以上のグループで作業する必要がある場合など)に行くために

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

他の方法を持っていますそれぞれ別々の列に置かれた複数のtsベクトルは、単一のクエリを使用して構築し、インデックスを作成します(複数のtsvector列に単一のインデックスを作成できます)。それは私が上で説明したものと似ていますが、おそらくそれほど効率が悪いでしょう。

これが役に立ちます。

+0

ありがとうございました@Nick。すぐにあなたの提案を見ていきます。 – rusllonrails

+1

スムースさが不明な場合は教えてください。 – Nick

+1

こんにちは@ニック大変ありがとうございます)私はあなたにアプローチしてテストしました。ありがとう、ずいぶん友だち – rusllonrails

関連する問題