無料スクリプト配布のPHP.TO   PHPの実用的なtips PHPマニュアル MySQLマニュアル Apacheマニュアル PostgreSQLマニュアル マニュアル検索    

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

setweight は、 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 データに含まれる語彙素(単語)各々の統計情報を返します。返却される列は以下のものです。

  • word text — 語彙素の値

  • ndoc integer — 単語が含まれる文書( tsvector )の数

  • nentry integer — 含まれる単語の数

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

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

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

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

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


powered by SEO.CUG.NET