PostgreSQL+Tsearch2+KAKASI分かち書きによる全文検索

PostgreSQL+tsearch2+pgkakasiw+KAKASI による全文検索を試してみました。
PostgreSQL 8.2 では GIN も登場していますし、
さらに 8.3 からは PostgreSQL 本体が全文検索機能を内蔵するという話もありますが、
まだまだ使われている環境も多そうな 7.4 や 8.1 系向けの構成ということで。

以下、必要なプログラムのインストールから全文検索のテストまでの手順メモです。
今回は、Debian+PostgreSQL 8.1.8 で試しました。

(1) KAKASI をインストールする

KAKASI は、日本語分かち書き用のライブラリです。
ソースからインストールする場合は、↓のソースをダウンロードします。
http://kakasi.namazu.org/stable/kakasi-2.3.4.tar.gz

Debian で apt-get でインストールする場合は↓で済み、楽でした。
# apt-get install libkakasi2 libkakasi2-dev
RPM なら kakasikakasi-dev が必要なようです。


(2) pgkakasiw をインストールする

pgkakasiw は、PostgreSQL から KAKASI を利用するための関数。
以下の手順でコンパイル・インストールします。
$ wget https://www.oss.ecl.ntt.co.jp/tsearch2j/source/pgkakasiw-1.1.tar.gz
$ tar xzf pgkakasiw-1.1.tar.gz
$ cd pgkakasiw-1.1
$ USE_PGXS=1 make
$ su
# USE_PGXS=1 make install
# /etc/rc3.d/S19postgresql-8.1 reload
ただし、--pgxs が利用できない場合は、Makefile の書き換えが必要で少し面倒。
wget 時に『--no-check-certificate』オプションが必要な場合も。


(3) 対象データベースに pgkakasiw を登録する

バイナリ pgkakasiw.so のインストールだけでは、分かち書きは利用できません。
pgkakasiw.sql を実行して、pgkakasiw 関数を登録する必要があります。
データベースの文字コードは、EUC_JP のみ利用可能です。
pgkakasiw.sql のパスは、環境により異なります。
$ psql -U postgres -f /usr/share/postgresql/8.1/contrib/pgkakasiw.sql testing_db
psql:pgkakasiw.sql:1: ERROR: function pgkakasiw(text) does not exist
CREATE FUNCTION
1行目の「ERROR」は、初回は必ず発生するので無視してOK。
2行目の「CREATE FUNCTION」があれば完了。


(4) 分かち書きを試してみる

pgkakasiw 関数で、実際の分かち書きの処理を試せます。
基本的に、概ね単語ごとに半角スペース区切りで分割されます。
$ psql testing_db
SELECT pgkakasiw('今日は良い天気です。');
pgkakasiw
--------------------------
今日は 良い 天気 です 。
(1 row)

(5) tsearch2 をインストール&登録する

tsearch2 が全文検索エンジンの本体です。
PostgreSQL 7.4 以降では PostgreSQL 本体のパッケージに添付されています。
手元の環境(標準的な .deb や .rpm の構成)では、もともとインストール済だったので省略。

ただし、バイナリ tsearch2.so のインストールに加えて、pgkakasiw と同様に
対象データベースごとに tsearch2 を登録する必要があります。
$ psql -U postgres -f /usr/share/postgresql/8.1/contrib/tsearch2.sql testing_db

(6) お試し用テーブルを作成する

こちら の手順を参考にして、テーブルを作成して下さい。
サンプルの tbl_zip テーブルには、以下のカラムがあります。(一部略)
zip_seqユニークキー
zip7郵便番号7桁
pref都道府県名
city市区町村名
area町域名(大字)

(6) tsearch2+pgkakasiw (KAKASI) による全文検索を試す

tsearch2 では、検索処理用データを格納する tsvector 型の専用カラムを用意して
そのカラムに対して、全文検索インデックスを作成します。
$ psql -U postgres testing_db

ALTER TABLE tbl_zip ADD COLUMN ts2_addr tsvector;
UPDATE tbl_zip SET ts2_addr = to_tsvector(pgkakasiw(pref||' '||city||' '||area));
VACUUM tbl_zip;
CREATE INDEX idx_zip_ts2_addr ON tbl_zip USING gist(ts2_addr);
この例では、ts2_addr カラムに都道府県名+市区町村名+町域名を格納しています。
この住所文字列を pgkakasiw で分かち書きし、to_tsvector で検索用に変換しています。
最後に、CREATE INDEX 文で ts2_addr にインデックスを作成しています。
EXPLAIN SELECT zip7, pref, city, area FROM tbl_zip WHERE
ts2_addr @@ to_tsquery('simple', replace(pgkakasiw('銀座'), ' ', '&'));
        QUERY PLAN
-----------------------------------------------------------------
Bitmap Heap Scan on tbl_zip (cost=2.42..433.70 rows=121 width=43)
  Filter: (ts2_addr @@ '''xxx'''::tsquery)
    -> Bitmap Index Scan on idx_zip_ts2_addr (cost=0.00..2.42 rows=121 width=0)
      Index Cond: (ts2_addr @@ '''銀座'''::tsquery)
EXPLAIN SELECT 文で検索時に正しくインデックスが利用されることを確認できました。
SELECT zip7, pref, city, area FROM tbl_zip WHERE
ts2_addr @@ to_tsquery('simple', replace(pgkakasiw('銀座'), ' ', '&'))
LIMIT 10;
  zip7   |  pref  |      city      |    area
---------+--------+----------------+------------
 3940022 | 長野県 | 岡谷市         | 銀座
 3950031 | 長野県 | 飯田市         | 銀座
 4240817 | 静岡県 | 静岡市清水区   | 銀座
 4480845 | 愛知県 | 刈谷市         | 銀座
 4750874 | 愛知県 | 半田市         | 銀座本町
 6128089 | 京都府 | 京都市伏見区   | 銀座町
 7450033 | 山口県 | 周南市         | みなみ銀座
 8040076 | 福岡県 | 北九州市戸畑区 | 銀座
 0691331 | 北海道 | 夕張郡長沼町   | 銀座
 1040061 | 東京都 | 中央区         | 銀座
(10 rows)
全国各地の「銀座」が含まれる地名が見つかります。
同じ条件を LIKE でも検索できます。
SELECT zip7, pref, city, area FROM tbl_zip WHERE
(pref||' '||city||' '||area) LIKE '%銀座%'
LIMIT 10;
以上は全文検索の確認でしたが、今回のスキーマ&この検索条件(銀座)に限れば、
area に btree インデックスを張って前方一致検索した方が高速かもしれません。
また、tsearch2 ではフレーズ単位の検索ができない点(連続性は無視される)や、
分かち書き誤りによる弊害(正しい単語に区切られない&漏れの発生)もあり、
注意が必要です。


(補足) レコード挿入・更新時に自動的にインデックスを更新させる

上記の例では、UPDATE 文で明示的にインデックス専用カラムを更新していました。
実際のアプリケーションでは、インデックス専用カラムを念頭に置いたコーディングは
したくありませんから、INSERT・UPDATE の際に新しいデータが自動的にインデックスにも
反映される仕組みが不可欠です。
CREATE TRIGGER test_idxupdate BEFORE UPDATE OR INSERT ON tbl_zip
FOR EACH ROW EXECUTE PROCEDURE tsearch2(ts2_addr, pgkakasiw, join_addr);
とすると、join_addr カラムが更新される際に自動的に ts2_addr カラム・インデックスも更新されます。

※ (pref||' '||city||' '||area) のような結合処理も同時に行う手順は、未確認です。
 

ブログ気持玉

クリックして気持ちを伝えよう!

ログインしてクリックすれば、自分のブログへのリンクが付きます。

→ログインへ

なるほど(納得、参考になった、ヘー)
驚いた
面白い
ナイス
ガッツ(がんばれ!)
かわいい

気持玉数 : 0

この記事へのコメント

この記事へのトラックバック