12.4. 追加機能

この節では、全文検索に関連する便利な追加の関数と演算子を説明します。

12.4.1. 文書の操作

項12.3.1に、もとのテキスト形式の文書がどのようにしてtsvectorに変換されるのか書いてあります。また、PostgreSQLではtsvector形式に変換済の文書を操作する関数と演算子が提供されています。

       tsvector || tsvector
      

tsvectorの結合演算子で、2つのベクターの語彙素と位置情報を合成し、tsvectorを返します。右辺のベクターの位置は左辺のベクターの一番大きな位置情報のオフセットになります。その結果、この関数の結果は、元の文書を結合したものにto_tsvectorを適用したものとほぼ同じになります(まったく同じと言うわけではありません。左辺の引数の最後の位置にあるストップワードは取り除かれるのに対し、テキストの結合が行われた場合は、その影響が右辺の引数にある語彙素位置に影響を与えるからです)。

to_tsvectorを適用する前のテキストを結合するよりも、ベクターを結合することの利点の一つは、文書の異なる部分をパースするために、異なる設定を使うことができることです。なお、setweight関数は与えられたベクターのすべての語彙素を同じ方法でマーク付けするため、もしも文書に異なる部分に別の重み付けを行いたいなら、結合する前に文書をパースしてsetweightを適用することが必要です。

       setweight(vector tsvector, weight "char") returns tsvector
      

この関数は、A, B, C, Dのいずれかの与えられたweightを入力のベクター中の位置にラベル付けし、そのコピーを返します(Dは新しいベクターのデフォルトで、出力する際には表示されません)。これらのラベルはベクターが結合される際にに保存されるので、ランキング関数によって文書中の異なる部分の語が別々に重み付けすることができます。

なお、重み付けラベルは語彙素ではなく位置に与えられることに注意してください。入力のベクターから位置が削除されていると、setweightは何もしません。

       length(vector tsvector) returns integer
      

ベクター中に格納されている語彙素の数を返します。

       strip(vector tsvector) returns tsvector
      

位置、重みの情報がないことを除けば入力のベクターと同じ語彙素のリストを持つベクターを返します。返却されたベクターは、情報を削除されていないベクターに比べてランキングに関しては、ずっと有用性が低くなりますが、通常非常に小さくなります。

12.4.2. 問合わせを操作する

項12.3.2は、元のテキストがいかにしてtsquery値に変換されるかを解説しています。またPostgreSQLは、tsquery形式に変換済の問合わせを操作するために使用できる関数と演算子を提供しています。

       tsquery && tsquery
      

2つの問合わせをANDで結合したものを返します。

       tsquery || tsquery
      

2つの問合わせをORで結合したものを返します。

       !! tsquery
      

与えられた問合わせの否定を返します。

       numnode(query tsquery) returns integer
      

tsquery中のノード(語彙素と演算子)の数を返します。この関数は、問合わせが意味のあるものか(返却値 > 0)、ストップワードだけを含んでいるか(返却値 0)を判断するのに役に立ちます。例を示します。

SELECT numnode(plainto_tsquery('the any'));
NOTICE:  query contains only stopword(s) or doesn't contain lexeme(s), ignored
 numnode
---------
       0

SELECT numnode('foo & bar'::tsquery);
 numnode
---------
       3

       querytree(query tsquery) returns text
      

インデックス検索の際に使用できるtsqueryの部分を返します。この関数は、たとえばストップワードのみ、あるいは否定語だけのように、インデックス検索できない問合わせを検出するのに役立ちます。例を示します。

SELECT querytree(to_tsquery('!defined'));
 querytree
-----------

12.4.2.1. 問合わせの書き換え

ts_rewriteファミリー関数は、与えられたtsqueryから目的の副問合わせ部分を探し、それを別の副問い合わせに置き換えます。本質的には、この操作は、部分文字列置き換えのtsquery版です。置き換え候補と置き換え内容の組は、問合わせ書き換えルールであると考えることができます。そのような書き換えルールの集合は、強力な検索ツールとなり得ます。たとえば、同義語(たとえばnew york, big apple, nyc, gotham)を使って問合わせをより広範囲にしたり、逆によりホットな話題にユーザを導くために問合わせを狭い範囲に絞ったりすることができます。この機能と、同義語辞書(項12.6.4)の間には、機能的な重複があります。しかし、再インデックス付けすることなしに、その場で書き換えルールを変更できるのに対し、同義語辞書の更新が有効になるためには、再インデックス付けを行わなければなりません。

        ts_rewrite (query tsquery, target tsquery, substitute tsquery) returns tsquery
       

この形式の ts_rewrite は、単純に単一の書き換えルールを適用します。query中に表れるtargetは、substituteですべて置き換えられます。例を示します。

SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);
 ts_rewrite
------------
 'b' & 'c'

        ts_rewrite (query tsquery, select text) returns tsquery
       

この形式のts_rewriteは、開始問合わせと、テキスト文字列で与えられるSQLのSELECTコマンドを受け取ります。SELECTは、tsquery型の2つの列を出力しなければなりません。現在の問合わせは、SELECTのそれぞれの結果行中の最初の列の結果(ターゲット)が、2番目の列の結果(置き換え値)に、置き換えられます。例を示します。

CREATE TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');

SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases');
 ts_rewrite
------------
 'b' & 'c'

なお、複数の書き換えルールを適用する際は、適用する順番が重要です。ですから、実際には並び替えのキーを適用するORDER BYを問合わせに入れておくのがよいでしょう。

天文学上の実際的な例を考えてみます。テーブル駆動の書き換えルールを使って、supernovaeを展開します。

CREATE TABLE aliases (t tsquery primary key, s tsquery);
INSERT INTO aliases VALUES(to_tsquery('supernovae'), to_tsquery('supernovae|sn'));

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
           ts_rewrite            
---------------------------------
 'crab' & ( 'supernova' | 'sn' )

テーブルを更新するだけで、書き換えルールを変更することができます。

UPDATE aliases SET s = to_tsquery('supernovae|sn & !nebulae') WHERE t = to_tsquery('supernovae');

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
                 ts_rewrite                  
---------------------------------------------
 'crab' & ( 'supernova' | 'sn' & !'nebula' )

書き換えルールが多くなると、書き換えが遅くなる可能性があります。なぜなら、書き換えの対象になるものを求めて、すべてのルールをチェックするからです。明らかに使われないルールを取り除くために、tsqueryの含有演算子を使うことができます。以下の例では、元の問合わせにマッチするルールだけを選ぶことができます。

SELECT ts_rewrite('a & b'::tsquery,
                  'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t');
 ts_rewrite
------------
 'b' & 'c'

12.4.3. 自動更新のためのトリガ

tsvector形式の文書を格納するために別の列を使う場合、文書の内容を格納した列が変更されたときにtsvectorを格納した列を更新するトリガを作っておく必要があります。この目的のために、2つの組み込み関数を利用できます。自分で関数を書くこともできます。

    tsvector_update_trigger(tsvector_column_name, config_name, text_column_name [, ... ])
    tsvector_update_trigger_column(tsvector_column_name, config_column_name, text_column_name [, ... ])
   

これらのトリガ関数は、1つ以上のテキスト列から、CREATE TRIGGERコマンドで指定されたパラメータの制御により、tsvector列を自動的に計算します。使い方の例を示します。

CREATE TABLE messages (
    title       text,
    body        text,
    tsv         tsvector
);

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);

INSERT INTO messages VALUES('title here', 'the body text is here');

SELECT * FROM messages;
   title    |         body          |            tsv             
------------+-----------------------+----------------------------
 title here | the body text is here | 'bodi':4 'text':5 'titl':1

SELECT title, body FROM messages WHERE tsv @@ to_tsquery('title & body');
   title    |         body          
------------+-----------------------
 title here | the body text is here

このトリガを作っておくことにより、 title またはbodyへの変更は、アプリケーションで考慮しなくても自動的にtsvに反映されます。

トリガの最初の引数は更新対象のtsvectorの列名でなければなりません。2番目の引数は、変換を実行する際に使用されるテキスト検索の設定です。tsvector_update_triggerでは、設定の名前は単に2番目のトリガ引数で与えられます。上で示すように、スキーマ修飾されていなければなりません。search_pathの変更がトリガの振る舞いに影響を与えないためです。tsvector_update_trigger_columnでは、2番目のトリガ引数は別のテーブル列の列名です。この列の型はregconfigでなければなりません。この方法により、設定を行単位で変えることができます。残りの引数はテキスト型(text, varchar, charのいずれか)の列の名前です。与えられた順に、文書中に取り込まれます。NULL値はスキップされます(ただし、それ以外の列はインデックス付けされます)。

組み込みトリガの制限事項として、すべての列を同じようにしか扱えないというものがあります。それぞれの列を違うように扱うには — たとえば本文とタイトルの重みを変えるとか —、カスタムトリガを書く必要があります。トリガ言語としてPL/pgSQLを使った例を示します。

CREATE FUNCTION messages_trigger() RETURNS trigger AS $$
begin
  new.tsv :=
     setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
     setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
  return new;
end
$$ LANGUAGE plpgsql;

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE PROCEDURE messages_trigger();

tsvector値をトリガ内で作るときには、設定名を明示的に与えることが重要であることを銘記しておいてください。そうすれば、default_text_search_configが変更されても列の内容は影響を受けません。これを怠ると、ダンプしてリロードすると検索結果が変わってしまうような問題が起きる可能性があります。

12.4.4. 文書の統計情報の収集

ts_stat関数は、設定をチェックしたり、ストップワードの候補を探すのに役立ちます。

    ts_stat(sqlquery text, [ weights text, ] OUT word text, OUT ndoc integer, OUT nentry integer) returns setof record
   

sqlqueryは単一のtsvector列を返すSQL問合わせのテキスト値です。ts_statは問合わせを実行し、tsvectorデータに含まれる語彙素(単語)各々の統計情報を返します。返却される列は以下のものです。

weightsが与えられていたら、その重みを持つものだけがカウントされます。

たとえば、文書中もっとも頻繁に現れる単語の上位10位を探すには以下のようにします。

SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

同じ例で、重みがABの単語だけをカウントするには、以下のようにします。

SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

アダルトレンタルサーバー