web-dev-qa-db-ja.com

JSONB列の単一の値に全文検索を実装するにはどうすればよいですか?

2人の会話を格納するテーブルがあります。

データは次のようになります。

CREATE TABLE foo
AS
  SELECT $$[
    { "user": 1, "timestamp": 1, "message": "First message" },
    { "user": 2, "timestamp": 2, "message": "Second message" },
    { "user": 2, "timestamp": 3, "message": "Debounced message from same user" },
    { "user": 1, "timestamp": 4, "message": "Last message" }
  ]$$::jsonb AS jsondata;

各メッセージを個別に検索する必要はないので、会話全体を1つのjsonbフィールドに格納します。すべてのメッセージに対して全文検索を実行する必要があります。

私の最初の考えは、新しいテキスト列を作成し、すべてのメッセージを1つの長い文字列に連結し、その列にトライグラムGINインデックスを作成することでした。

スペースを浪費するハックのようですので、中間のコラムは避けたいと思います。 jsonb列から直接インデックスを作成するにはどうすればよいですか?

5
ndbroadbent

私がこの質問を読んだ方法では、あなたはmessageだけを気にします。ここでの難しさは、あなたがする必要があることです、

  1. メッセージ要素を返すjson配列にマップします
  2. メッセージ要素文字列の配列を集約文字列に縮小/折りたたみます。

これは関数型プログラミングでは簡単です。 PostgreSQLのストック関数を使用するのは簡単ではなく、宣言型言語で機能させることは困難です。たぶんいつかあなたはjsonb_array_elements(jsonb [,path])を手にするでしょうが、それまではデータベースに関数を作成することができます。

Plpgsqlで関数を作成する

これはおそらくplv8関数ほど高速でもクリーンでもありませんが、次のリビジョンではtsvectorを返します。

ここでは、jsonb_array_elementsを使用してjsonを展開し、'message'要素を文字列に集約します。

CREATE OR REPLACE FUNCTION jsonb_message_to_string( jsondata jsonb, out string text )
AS $func$
  BEGIN
    SELECT INTO string
      string_agg(d->>'message', ' ')
    FROM jsonb_array_elements(jsondata) AS d;
    RETURN;
  END;
$func$ LANGUAGE plpgsql
IMMUTABLE;

tsvector_aggの作成と機能の改善。

この関数は文字列を返すため、まだ最適ではありません。ただし、9.6の時点でPostgreSQLにはtsvector_aggがまだ付属していないという2番目の問題があります。しかし、それはPostgreSQLなので、作成できます。

CREATE AGGREGATE tsvector_agg (tsvector) (
  SFUNC = tsvector_concat,
  STYPE = tsvector
);

これにより、より高速で位置情報を保持する集約tsvectorを返すことができます。これで機能を改善できます。ここでは、新しいjsonb_message_to_tsvectorを作成します。

CREATE OR REPLACE FUNCTION jsonb_message_to_tsvector( jsondata jsonb, out tsv tsvector )
AS $func$
  BEGIN
    SELECT INTO tsv
      tsvector_agg(to_tsvector(d->>'message'))
    FROM jsonb_array_elements(jsondata) AS d;
    RETURN;
  END;
$func$ LANGUAGE plpgsql
IMMUTABLE;

これで、インデックスを作成できます。

CREATE INDEX ON FOO
  USING gin (jsonb_message_to_tsvector(jsondata));

そして、そのようにクエリします。

SELECT jsonb_message_to_tsvector(jsondata) @@ 'first'
FROM foo;
7
Evan Carroll

ここに例があります:

t=# create table so59(j jsonb);
CREATE TABLE
t=# insert into so59 select '[
  { "user": 1, "timestamp": 1, "message": "First message" },
  { "user": 2, "timestamp": 2, "message": "Second message" },
  { "user": 2, "timestamp": 3, "message": "Debounced message from same user" },
  { "user": 1, "timestamp": 4, "message": "Last message" }
]
';
INSERT 0 1
t=# create index so60 on so59 using gin(to_tsvector('english',j::text));
CREATE INDEX

pdate: jsonb配列をテキストにストリップする簡単な関数を作成できます。例:

t=# create or replace function so61(j jsonb) returns text as
$$
with a as (select jsonb_array_elements(j)->>'message' m) select string_agg(m,',') from a;
$$ language sql;
CREATE FUNCTION
t=# select so61(j) from so59;
                                    so61
----------------------------------------------------------------------------
 First message,Second message,Debounced message from same user,Last message
(1 row)
t=# create index so61 on so59 using gin(to_tsvector('english',so61(j)));
1
Vao Tsun