これはPGroonga 2.X and 3.X用のドキュメントです。古いPGroongaを使っているならPGroonga 1.xのドキュメントを見てください。

pgroonga_query_expand関数

1.2.2で追加。

概要

pgroonga_query_expand関数はクエリー構文を使ったクエリー内にある登録済みの同義語を展開します。クエリー構文は&@~演算子&@~|演算子で使われています。

pgroonga_query_expand関数はクエリー展開機能を実現するときに便利です。Groongaのクエリー展開機能のドキュメントも参照してください。

構文

この関数の構文は次の通りです。

text pgroonga_query_expand(table_name,
                           term_column_name,
                           synonyms_column_name,
                           query)

table_nametext型の値です。同義語を格納している既存のテーブルの名前を指定します。

term_column_nametext型の値です。table_nameテーブル内の展開対象の単語を格納しているカラムの名前を指定します。このカラムはtext型かtext[]型のカラムです。

synonyms_column_nametext型の値です。termカラムの同義語を格納しているカラム名を指定します。このカラムはtext[]型のカラムです。

querytext型の値です。クエリー構文を使っているクエリーです。

pgroonga_query_expandtext型の値を返します。query中にある登録済みの同義語がすべて展開されています。

次のようにpgroonga_text_term_search_ops_v2演算子クラス指定のPGroongaで${table_name}.${term_column_name}のインデックスを作成することをオススメします。これは高速にクエリー展開できるようにするためです。

CREATE TABLE synonyms (
  term text,
  synonyms text[]
);

CREATE INDEX synonyms_term
          ON synonyms
       USING pgroonga (term pgroonga_text_term_search_ops_v2);

pgroonga_query_escape関数はインデックスなしでも動きますが、インデックスがあるとより高速に動きます。

btreeのようにtext型の=に対応しているインデックスアクセスメソッドであればどのインデックスアクセスメソッドでも使えます。しかし、PGroongaを使うことをオススメします。なぜなら、PGroongaはtextの値を正規化した=(大文字小文字を無視した比較を含む)に対応しているからです。クエリー展開時は値を正規化した=が便利です。

使い方

次のスタイルを使えます。

1単語を複数の同義語にマッピング

サンプルスキーマとデータは次の通りです。

CREATE TABLE synonyms (
  term text,
  synonyms text[]
);

CREATE INDEX synonyms_term
          ON synonyms
       USING pgroonga (term pgroonga_text_term_search_ops_v2);

INSERT INTO synonyms VALUES ('PGroonga', ARRAY['PGroonga', 'Groonga PostgreSQL']);

このサンプルではPGroongaインデックスを使っているのでクエリー中の"PGroonga""pgroonga"もすべて展開されます。

SELECT pgroonga_query_expand('synonyms', 'term', 'synonyms',
                             'PGroonga OR Mroonga') AS query_expand;
--                  query_expand                   
-- -------------------------------------------------
--  ((PGroonga) OR (Groonga PostgreSQL)) OR Mroonga
-- (1 row)
SELECT pgroonga_query_expand('synonyms', 'term', 'synonyms',
                             'pgroonga OR mroonga') AS query_expand;
--                   query_expand                   
-- -------------------------------------------------
--  ((PGroonga) OR (Groonga PostgreSQL)) OR mroonga
-- (1 row)

同義語グループ

2.2.1で追加。

サンプルスキーマとデータは次の通りです。

CREATE TABLE synonym_groups (
  synonyms text[]
);

CREATE INDEX synonym_groups_synonyms
          ON synonym_groups
       USING pgroonga (synonyms pgroonga_text_array_term_search_ops_v2);

INSERT INTO synonym_groups
  VALUES (ARRAY['PGroonga', 'Groonga']);

このサンプルではPGroongaインデックスを使っているのでクエリー中の"PGroonga""pgroonga"もすべて展開されます。

SELECT pgroonga_query_expand('synonym_groups', 'synonyms', 'synonyms',
                             'PGroonga OR Mroonga') AS query_expand;
--              query_expand             
-- --------------------------------------
--  ((PGroonga) OR (Groonga)) OR Mroonga
-- (1 row)
SELECT pgroonga_query_expand('synonym_groups', 'synonyms', 'synonyms',
                             'pgroonga OR mroonga') AS query_expand;
--              query_expand             
-- --------------------------------------
--  ((PGroonga) OR (Groonga)) OR mroonga
-- (1 row)

同義語グループを使った検索の具体例

人名テーブルの中から同じ読み方で漢字の異なる人を探すサンプルです。(例:斉藤, 齊藤, 斎藤, 齋藤)

サンプルとなるスキーマとデータは次の通りです。

人名テーブル

CREATE TABLE names (
  name varchar(255)
);

CREATE INDEX pgroonga_names_index
          ON names
       USING pgroonga (name pgroonga_varchar_full_text_search_ops_v2);

INSERT INTO names
  (name)
  VALUES ('斉藤'),('齊藤'),('斎藤'),('鈴木'),('田中'),('佐藤');

同義語テーブル

CREATE TABLE synonym_groups (
  synonyms text[]
);

CREATE INDEX synonym_groups_synonyms
          ON synonym_groups
       USING pgroonga (synonyms pgroonga_text_array_term_search_ops_v2);

INSERT INTO synonym_groups
  VALUES (ARRAY['斉藤', '齊藤', '斎藤', '齋藤']);

このサンプルでは「斉藤」で検索することでnamesテーブル内データの"斉藤""齊藤""斎藤"もすべて以下のように展開されます。

SELECT pgroonga_query_expand('synonym_groups', 'synonyms', 'synonyms',
                             '斉藤') AS query_expand;
--              query_expand             
-- --------------------------------------
--   ((斉藤) OR (齊藤) OR (斎藤) OR (齋藤))
-- (1 row)

この例では、人名テーブルの検索をするので以下のように検索します。

注意: 下記の例では、検索対象のカラムがvarchar型なので、pgroonga_query_expand(...)::varcharとしてpgroonga_query_expandの結果を明示的にキャストする必要があります。(pgroonga_query_expand()の戻り値の型はtext型なので、検索対象のカラムがtext型の場合はキャストは不要です。)

このようにキャストしないと、検索対象のカラムとpgroonga_query_expand()の型が異なりシーケンシャルサーチになるため、パフォーマンスが出ません。

SELECT name AS synonym_names from names where name &@~ pgroonga_query_expand(
                             'synonym_groups', 'synonyms', 'synonyms','斉藤')::varchar;
--  synonym_names             
-- -----------------
--      斉藤
--      齊藤
--      斎藤
--(3 rows)

-- varcharでのキャストが無い時のEXPLAIN ANALYZE結果(シーケンシャルサーチが使われる):
EXPLAIN ANALYZE VERBOSE SELECT name AS synonym_names from names where name &@~ pgroonga_query_expand(
                             'synonym_groups', 'synonyms', 'synonyms','斉藤');
--                                                               QUERY PLAN                                                               
-- ---------------------------------------------------------------------------------------------------------------------------------------
--  Seq Scan on public.names  (cost=0.00..124.38 rows=1 width=516) (actual time=66.684..67.619 rows=3 loops=1)
--    Output: name
--    Filter: ((names.name)::text &@~ pgroonga_query_expand('synonym_groups'::cstring, 'synonyms'::text, 'synonyms'::text, '斉藤'::text))
--    Rows Removed by Filter: 3
--  Planning Time: 0.420 ms
--  Execution Time: 67.750 ms
-- (6 rows)


-- varcharでキャスト時のEXPLAIN ANALYZE結果(インデックス利用に注目):
EXPLAIN ANALYZE VERBOSE SELECT name AS synonym_names from names where name &@~ pgroonga_query_expand(
                             'synonym_groups', 'synonyms', 'synonyms','斉藤')::varchar;
--                                                                           QUERY PLAN                                                                          
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------
--  Bitmap Heap Scan on public.names  (cost=0.00..29.06 rows=1 width=516) (actual time=2.212..2.214 rows=3 loops=1)
--    Output: name
--    Recheck Cond: (names.name &@~ (pgroonga_query_expand('synonym_groups'::cstring, 'synonyms'::text, 'synonyms'::text, '斉藤'::text))::character varying)
--    Heap Blocks: exact=1
--    ->  Bitmap Index Scan on pgroonga_names_index  (cost=0.00..0.00 rows=25 width=0) (actual time=2.197..2.198 rows=3 loops=1)
--          Index Cond: (names.name &@~ (pgroonga_query_expand('synonym_groups'::cstring, 'synonyms'::text, 'synonyms'::text, '斉藤'::text))::character varying)
--  Planning Time: 3.855 ms
--  Execution Time: 2.447 ms
-- (8 rows)

参考