2007年03月30日

●Tsearch2で全文検索

ほぼ1年ぶりの更新となってしまいました。
今回はPostgreSQLにおける全文検索機能として、Tsearch2を利用する方法を解説します。
インストールについて昔はえらくめんどくさかったのですが、Postgresの最新版(07/03/30現在 Ver8.2.3)ではTsearch2が同梱されているためとっても簡単になっています。

Windows版での説明です。

公式サイトから「postgresql-8.2.3-1-ja.zip」をダウンロード・展開して、「postgresql-8.2-ja.msi」を実行します。

インストーラーでは、「ようこそ」-「注意事項」-「インストールオプション」-「サービス構成」-「クラスタの初期化」-「手続き言語」までは通常の手順で進めます。

次の「貢献モジュールを可能にする」で、一番右の列にある「Tsearch2」のチェックをONにします。

あとは先に進んでインストールを完了させるだけ。簡単!!すばらしい!!

では検索用のテーブルを作成してみましょう。

CREATE TABLE tbl (
key numeric(2) primary key,
origtext TEXT,
wakachi TSVECTOR
);

CREATE INDEX wakachi_index ON tbl USING gin (wakachi);

「wakachi」列には検索キーワードを格納する列です。
列のタイプは「TSVECTOR」とします。

「wakachi」列に対してインデックス「wakachi_index」を作成します。
「USING gin」はインデックスのタイプを指定しています。
gin は汎用転置インデックスです。

データを投入しましょう。「origtext」列には元の文書を、「wakachi」列にはわかち書きされたキーワードを投入します。Tsearch2自体にはわかち書きする機能はないそうなので、ここではわかち書きが既に為されているという前提でINSERTします。(javaでわかち書きする方法は別エントリで説明します)

insert into tbl values(1,
'ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!',
'ルウム 戦役 で 5 隻 の 戦艦 が シャア 一 人 為 に 撃破 する れる た ... 、 逃げ ろ ーッ !');
insert into tbl values(2,'
このタイミングで戦闘を仕掛けたと言う事実は、古今例が無い。',
'この タイミング で 戦闘 を 仕掛ける た と 言う 事実 は 、 古今 例 が 無い 。');
insert into tbl values(3,
'そのために私のような女を大佐は拾って下さったんでしょう?',
'その ため に 私 の よう だ 女 を 大佐 は 拾う て 下さる た ん です う ?');
insert into tbl values(4,
'赤い色のMS!シャアじゃないのか?',
'赤い 色 の M S ! シャア じゃ ない か ?');
insert into tbl values(5,
'一年戦争開戦初期、1 月 15 日からサイド 5 (ルウム) にて行われた一大艦隊戦役',
'一 年 戦争 開戦 初期 、 1 月 15 日 から サイド 5 ( ルウム ) にて 行う れる た 一大 艦隊 戦役');

わかち書きしたキーワードは、半角スペースを空けて登録します。

で、検索してみましょう。

SELECT key,origtext FROM tbl WHERE 'シャア'::TSQUERY @@ wakachi;

「シャア」を含むkey「1」「4」が検索されるはずです。
複数のキーワードでも検索できます。

SELECT key,origtext FROM tbl WHERE 'シャア&赤い'::TSQUERY @@ wakachi;

「シャア」と「赤い」を両方含むkey「4」のみが検索されます。
ちなみにAND検索は「&」,OR検索は「|」を区切りに使います。

ただしこれだけではまだ不十分です。たとえば「ルウム戦役」というキーワードで検索したい場合、わかち書きされたキーワードは「ルウム」と「戦役」に分けられています。
よって

SELECT key,origtext FROM tbl WHERE 'ルウム戦役'::TSQUERY @@ wakachi;

ではヒットしません。検索する場合は、「検索キーワードも事前にわかち書きしておく」のが鉄則です。SQL文もちょっと工夫します。

SELECT key,origtext
FROM (
SELECT *
FROM tbl
WHERE 'ルウム&戦役'::TSQUERY @@ wakachi OFFSET 0
) AS findtbl
WHERE origtext LIKE '%ルウム戦役%'

検索前に「ルウム戦役」をわかち書きして「ルウム」と「戦役」を取得しておきます。
副問い合わせの中側では「ルウム&戦役」でAND検索するようにします。
これだけだと「ルウム」「戦役」の両方を含む行が全て(key「1」と「5」)返されてしまいます。行いたいのは「ルウム戦役」の検索ですから、副問い合わせの外側で「WHERE origtext LIKE '%ルウム戦役%'」とさらに絞り込んでkey「1」が返るようになります。

「OFFSET 0」はプランナに必ずインデックスを使用することを強制させる意味合いをもちます。
(「PostgreSQL + Tsearch2 日本語化パッチによる日本語全文検索システム構築手順」寺本純司 NTT サイバースペース研究所OSS コンピューティングプロジェクトより)

いやぁ便利な世の中になったもんです。