全文検索(または単にテキスト検索)は、問合わせを満たす自然言語の文書を識別し、更には問合わせとの関連性の順に並び替えることができます。もっとも一般的な検索は、与えられた検索語を含む文書を探し、問合わせとの類似性の順に返す、というものです。問合わせと類似性の記法は非常に柔軟で、特定の用途に適合できます。もっとも単純な検索では、問合わせは単語の集合として、類似性は文書中の問合わせ対象の単語の頻度として扱います。
テキスト検索演算子は、データベースシステムに長年存在していました。PostgreSQLは、テキストデータ型用に、~,~*, LIKE,ILIKEの各演算子を持っています。しかし、近代的な情報システムに必要な以下の本質的な特徴を欠いています。
英語にさえ、言語学的なサポートがありません。派生語、たとえばsatisfiesに対してsatisfyを容易に扱えないため、正規表現は十分ではありません。satisfyを探すときは、たぶんあなたはsatisfiesも探したいでしょうが、それらを含む文書は探せないかもしれません。ORを使えば複数の派生語を検索することができますが、退屈で間違いやすいです(ある種の単語は数千の派生語を持つことがあります)。
検索結果を順序付け(順位付け)することができません。その結果、数千の合致する文書が見つかったような場合に非効率的です。
インデックスをサポートしないので毎回検索時にすべての文書を処理しなければならず、遅いです。
全文検索のインデックス付けでは、文書を前もって処理しておき、後で素早く検索するために、インデックスを保存しておくことができます。前処理には以下があります。
文書からトークンを解析します。 トークンを色々なクラス、たとえば数、単語、複合単語、電子メールアドレスに分けて識別することが有効です。そうすれば、扱いを変えることができます。原則として、トークンのクラスは、特定の用途に依存します。しかし、ほとんどの目的には、あらかじめ定義されたクラスの集合を使うのが適当です。PostgreSQLは、パーサを使ってこの処理段階を実行します。標準搭載のパーサが提供されますが、特別な用途にはカスタム仕様のパーサを作ることもできます。
トークンを語彙素(lexemes)に変換します。語彙素はトークンと同じ文字列ですが、違う形態の同じ単語が同じになるように 正規化されています。たとえば、正規化においてはほぼ常に大文字を小文字に変換し、接尾を取り除くことが多いです(英語のsまたはes)。これにより、可能性のあるすべての変種を地道に入力すること無く、同じ単語の変化形を検索できます。また、このステップでは、あまりにありふれていて、検索の役に立たないストップワードを取り除くことが多いです。(つまり、トークンは文書テキストの未加工の断片そのものであり、語彙素はインデックス付けや検索に有用と思われる単語です。)PostgreSQLは、辞書を使ってこのステップを実行します。いろいろな標準辞書が提供されています。特定の用途向けにカスタム辞書を作ることもできます。
検索に最適化された前処理済の文書を保存します。たとえば、個々の文書は、正規化された語彙素の整列済の配列として表現されます。語彙素とともに、適合性ランキング用に、位置情報を格納しておくことがしばしば望まれます。そうすることにより、問合わせの語を"高密度"に含んでいる文書を、まばらに含む文書よりも高くランクづけすることができます。
辞書を使ってトークンの正規化を細かく制御できます。適当な辞書を用意すれば次のようなことができます。
インデックスしたくないストップワードの定義
Ispellを使って、同義語を単一の単語に関連づける
類語辞書(thesaurus)を使って、成句を単一の単語に関連づける
Ispell辞書を使って、単語の変種を正規の単語に関連づける
Snowball語幹規則を使って、単語の変種を正規の単語に関連づける
前処理した文書を格納するために、データ型tsvectorが提供されています。また、処理済問合わせを表現するためにtsquery型も提供されています(項8.11)。これらのデータ型のために、多数の関数と演算子が利用できますが(項9.13)、もっとも重要なのは、項12.1.2で紹介している@@演算子です。全文検索はインデックス(項12.9)を使って高速化できます。
文書は全文検索システムにおける検索の単位です。たとえば、雑誌記事やメールのメッセージです。テキスト検索エンジンは、文書をパースし、語彙素(キーワード)とそれが含まれる親文書の関連を格納できなければなりません。後で、この関連を使って問合わせ語を含む文書を検索するのに使います。
PostgreSQLでの検索においては、ドキュメントはデータベースのテーブルの行内のテキストフィールドか、あるいはそのようなフィールドの組み合わせ(結合)でもよいです。そうしたフィールドはおそらく複数のテーブルに格納されていたり、動的に獲得されるものであったりします。言い換えると、文書はインデックス付けのために複数の異なる部分から構成されても良く、それらが全体としてはひとまとまりに格納されていなくても良いのです。例を示します。
SELECT title || ' ' || author || ' ' || abstract || ' ' || body AS document FROM messages WHERE mid = 12; SELECT m.title || ' ' || m.author || ' ' || m.abstract || ' ' || d.body AS document FROM messages m, docs d WHERE mid = did AND mid = 12;
注意: 実際には、これらの例の問合わせでは、
coalesce
を使って、一部NULLが含まれているためにドキュメント全体がNULLになってしまうのを防ぐべきです。
別な方法としては、ファイルシステム上に文書を単純なテキストファイルとして格納することです。この場合、データベースは、フルテキストインデックスを格納し、検索を実行するために使うことができます。ファイルシステムから文書を取り出すためには、何かのユニークな識別子を使います。しかし、データベースの外にあるファイルを取り出すには、スーパユーザの許可か、特殊な関数のサポートが必要です。そういうわけでたいていの場合はPostgreSQLの中にすべてのデータを保持するのよりも不便です。また、すべてのデータをデータベースに保持することにより、文書のインデックス付けと表示の際に文書のメタデータにアクセスすることが容易になります。
テキスト検索という目的のため、各々の文書は前処理されてtsvector形式に変換しておかなければなりません。検索と順位付けはすべてtsvector表現の文書上で行われます。検索とランキングは文書のtsvector表現上で実行されます — オリジナル文書は、ユーザに表示のため選択された場合にのみ取り出される必要があります。というわけで、ここではtsvectorを文書と見なすことがよくあります。といっても、tsvectorは完全な文書の縮小表現でしかありません。
PostgreSQLにおける全文検索は、tsvector(文書)が、tsquery(問合わせ)に一致したら真を返す照合演算子@@に基づいています。どちらのデータ型を先に書いても構いません。
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery; ?column? ---------- t SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector; ?column? ---------- f
上記の例でわかるように、tsqueryは、tsvectorと違って、単なるテキストではありません。tsqueryは正規化済の語彙素である検索表現を含み、AND, OR, NOT演算子を使って複数の表現を組み合わせても構いません。(詳細はこれを見てください 項8.11。)
たとえば、テキスト中の単語を正規化することにより、ユーザが入力したテキストを適切なtsqueryに変換するto_tsquery
とplainto_tsquery
という関数があります。同様に、文書文字列をパースして正規化するためにto_tsvector
が利用できます。というわけで、実際にはテキスト検索照合はこんな感じになります。
SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat'); ?column? ---------- t
この照合は、もしつぎのように書くとうまくいかないことに注意してください。
SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat'); ?column? ---------- f
というのも、単語ratsに対して正規化が行われないからです。tsvectorの要素は、すでに正規化されている語彙素であることになっているので、ratsはratに一致しません。
また、@@演算子は、textを入力として受付けるので、簡単に使うときには、明示的にテキスト文字列をtsvectorまたはtsqueryに変換することを省略できます。応用として以下のものがあります。
tsvector @@ tsquery tsquery @@ tsvector text @@ tsquery text @@ text
今までのはすべて単純なテキスト検索の例でした。すでに述べたように、全文検索機能を使えば、もっと色々なことができます。インデックス付けの際に特定の単語をスキップ(ストップワード)、同義語(synonym)処理、賢いパース処理、すなわち、単に空白区切りに基づくパース処理以上のものです。この機能はテキスト検索設定で制御します。PostgreSQLには、多くの言語用の設定があらかじめ組み込まれていますが、ユーザ設定を容易に作ることもできます。(psqlの\dFコマンドで、利用できる設定を表示できます。
インストールの際には、適当な設定が選ばれ、default_text_search_configがpostgresql.conf中にセットされます。クラスタ全体で同じ設定を使用する場合はpostgresql.confの設定値を利用できます。クラスタの設定とは異なるが、あるデータベースの中で同じ設定を使う場合には、ALTER DATABASE ... SETを利用します。さもなければ、セッション単位でdefault_text_search_configを設定できます。
設定に依存するテキスト検索関数は、オプションでregconfig引数を持っており、使用する設定を明示的に指定できます。default_text_search_configは、この引数が省略されたときだけ使用されます。
カスタムテキスト検索設定を作り易くするため、設定はより単純なデータベースオブジェクトから作られます。PostgreSQLのテキスト検索機能は、4つの設定関連のデータベースオブジェクトを提供しています。
テキスト検索パーサは、文書をトークンに分解し、トークンを分類します(たとえば、単語とか数のように)。
テキスト検索辞書はトークンを正規化された形式に変換し、ストップワードを排除します。
テキスト検索テンプレートは、現在の辞書が利用する関数を提供します(辞書は、単にテンプレートと、その引数の集合を指定するだけです)。
テキスト検索設定は、パーサと使用する辞書の集合を選択し、パーサが生成したトークンを正規化します。
テキスト検索パーサとテンプレートは、低レベルのC関数で作ります。したがって、新しく開発するためにはCのプログラミング能力と、データベースにインストールするためのスーパユーザ権限が必要になります。(PostgreSQLの配布物のcontrib/には、追加パーサとテンプレートの例があります)。辞書と設定は、単に配下のパーサとテンプレートのパラメータを設定し、両者を結び付けるだけなので、新しい辞書と設定を作るために特別な権限は必要ありません。この章の後でカスタム辞書と設定を作る例が登場します。