この節では、全文検索に関連する便利な追加の関数と演算子を説明します。
12.3.1に、もとのテキスト形式の文書がどのようにしてtsvectorに変換されるのか書いてあります。また、PostgreSQLではtsvector形式に変換済の文書を操作する関数と演算子が提供されています。
tsvector || tsvector
tsvectorの結合演算子で、2つのベクトルの語彙素と位置情報を合成し、ベクトルを返します。
位置と重み付けラベルは、結合では維持されます。
右辺のベクトルの位置は左辺のベクトルの一番大きな位置情報のオフセットになります。その結果、この関数の結果は、元の2つの文書文字列を結合したものに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
入力のベクトルと同じ語彙素のリストを持つが、位置と重みの情報が全くないベクトルを返します。
その結果は、通常は情報を削除されていないベクトルよりもずっと小さくなりますが、有用性も低くなります。
また、tsquery演算子<-> (FOLLOWED BY)は情報を削除した入力とマッチすることはありません。
なぜなら語彙素が発生する間の距離を決定できないからです。
tsvectorに関連した関数の完全なリストが表 9.43にあります。
12.3.2は、元のテキストがいかにしてtsquery値に変換されるかを解説しています。またPostgreSQLは、tsquery形式に変換済の問い合わせを操作するために使用できる関数と演算子を提供しています。
tsquery && tsquery
2つの問い合わせをANDで結合したものを返します。
tsquery || tsquery
2つの問い合わせをORで結合したものを返します。
!! tsquery
与えられた問い合わせの否定を返します。
tsquery <-> tsquery
1番目の問い合わせにマッチし、その直後に2番目の問い合わせにマッチするものを検索する問い合わせを、tsquery演算子<-> (FOLLOWED BY) を使って返します。
例を示します。
SELECT to_tsquery('fat') <-> to_tsquery('cat | rat');
?column?
----------------------------
'fat' <-> ( 'cat' | 'rat' )
tsquery_phrase(query1 tsquery, query2 tsquery [, distance integer ]) returns tsquery
1番目の問い合わせにマッチし、その後にちょうどdistance個の語彙素の距離で2番目の問い合わせにマッチするものを検索する問い合わせを、tsquery演算子<を使って返します。
例を示します。
N>
SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10);
tsquery_phrase
------------------
'fat' <10> 'cat'
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
-----------
'defin'
SELECT querytree(to_tsquery('!defined'));
querytree
-----------
T
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は、開始queryと、テキスト文字列で与えられるSQLのselectコマンドを受け取ります。
selectは、tsquery型の2つの列を出力しなければなりません。
現在のquery値は、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.2.2で説明するように、格納された生成列の使用に置き換えられました。
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 FUNCTION
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 FUNCTION messages_trigger();
tsvector値をトリガ内で作るときには、設定名を明示的に与えることが重要であることを銘記しておいてください。そうすれば、default_text_search_configが変更されても列の内容は影響を受けません。これを怠ると、ダンプしてリストアすると検索結果が変わってしまうような問題が起きる可能性があります。
ts_stat関数は、設定をチェックしたり、ストップワードの候補を探すのに役立ちます。
ts_stat(sqlquerytext, [weightstext, ] OUTwordtext, OUTndocinteger, OUTnentryinteger) returnssetof 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;