$ make # cp libtsja.so /usr/local/pgsql/lib
次にデータベースを生成し、テキスト検索パーサー/辞書/設定を登録する。
$ createdb db_tsja $ psql -d db_tsja -f dbinit_libtsja.txt
テキスト検索辞書として、英語部分についてはPostgreSQL同梱のsnowball_dictを用いるようにしている。用途に応じて設定を変更するとよい。
検索対象のテーブルを用意し、インデックスを設定する。以下はGINインデックスの例。
$ psql -d db_tsja db_tsja=# CREATE TABLE terms_tbl (... db_tsja-# (中略) db_tsja-# j_term text, db_tsja-# j_description text, db_tsja-# (中略) db_tsja-# ); db_tsja=# CREATE INDEX terms_tbl_idx ON terms_tbl db_tsja-# USING gin(to_tsvector('japanese', j_description));
すると次のようにして検索できるようになる。実際に使う際には、検索キーワード部分をパラメーターにしておくとよい。
db_tsja=# SELECT j_term, db_tsja-# ts_headline('japanese', j_description, to_tsquery('japanese', '日本語&検索'), 'StartSel=<em>,StopSel=</em>'), db_tsja-# ts_rank(to_tsvector('japanese', j_description), to_tsquery('japanese', '日本語&検索')) AS rank db_tsja-# FROM terms_tbl db_tsja-# WHERE to_tsvector('japanese', j_description) @@ to_tsquery('japanese', '日本語&検索') db_tsja-# ORDER BY rank DESC ;
to_tsvector()、to_tsquery()、ts_rank()などの函数については、PostgreSQL付属ドキュメントの「全文検索」章を参照。
dbinit_libtsja.txtの処理内容をもう少し詳しく説明する。
CREATE FUNCTION tsja_start(internal, int4) RETURNS internal AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE FUNCTION tsja_gettoken(internal, internal, internal) RETURNS internal AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE FUNCTION tsja_end(internal) RETURNS void AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE FUNCTION tsja_lextypes(internal) RETURNS internal AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE TEXT SEARCH PARSER japanese ( START = tsja_start, GETTOKEN = tsja_gettoken, END = tsja_end, HEADLINE = pg_catalog.prsd_headline, LEXTYPES = tsja_lextypes );
ここに並んでいる4つの函数が、tsja.cに実装されている。HEADLINEに設定した函数は、PostgreSQLにはじめから備わっているものである。
tsja_start()、tsja_gettoken()、tsja_end()は、日本語文を単語に分解し、個々の単語の品詞情報を添えて返す。
tsja_lextypes()は、このパーサーが認識できる品詞の一覧を返す。名詞や動詞であっても、検索に用いるには適切でない単語が若干あるので、普通にいう「品詞」より少し細かく分類する。
CREATE TEXT SEARCH DICTIONARY simple_dict ( TEMPLATE = pg_catalog.simple, STOPWORDS = english ); CREATE TEXT SEARCH DICTIONARY snowball_dict ( TEMPLATE = snowball, Language = english, StopWords = english ); CREATE FUNCTION tsja_init(internal) RETURNS internal AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE FUNCTION tsja_lexize(internal, internal, internal, internal) RETURNS internal AS '$libdir/libtsja' LANGUAGE 'c' STRICT; CREATE TEXT SEARCH TEMPLATE mecab ( INIT = tsja_init, LEXIZE = tsja_lexize ); CREATE TEXT SEARCH DICTIONARY japanese_dict ( TEMPLATE = mecab );
simple_dict、snowball_dictは、PostgreSQLに付属している、主に英語 (をはじめとする印欧語族の言語) を対象とする辞書である。ASCII文字のみから成る単語は英語と看做し、この辞書で処理する。
snowball_dictはドイツ語やフランス語なども扱える。それを考慮すれば、「ASCII文字のみ」という判定基準は不充分であるが、当面は保留としておく。
tsja_init()、tsja_lexize()は、単語を終止形に正規化する。たとえば、表層形は同じでも、それが名詞か動詞かによって正規形が異なることもあるので、パーサーと連携して処理することになる。
CREATE TEXT SEARCH CONFIGURATION japanese ( PARSER = japanese ); ALTER TEXT SEARCH CONFIGURATION japanese ADD MAPPING FOR word WITH snowball_dict; ALTER TEXT SEARCH CONFIGURATION japanese ADD MAPPING FOR 名詞, 動詞, 形容詞, 副詞, 連体詞 WITH japanese_dict; ALTER TEXT SEARCH CONFIGURATION japanese ADD MAPPING FOR 非自立名詞, 代名詞, 数, 助数詞, 非自立動詞, 非自立形容詞, 助詞類接続副詞 WITH japanese_dict; ALTER TEXT SEARCH CONFIGURATION japanese ADD MAPPING FOR 助詞, 助動詞, 接続詞, 接頭詞, 感動詞 WITH japanese_dict; ALTER TEXT SEARCH CONFIGURATION japanese ADD MAPPING FOR 記号, フィラー, その他 WITH japanese_dict;
品詞の判定結果によって使い分けをしている。この設定例では、英語部分の処理をPostgreSQLに付属のsnowball_dictに委ねている。
CREATE FUNCTION pg_to_ruby(text) RETURNS text AS '$libdir/libtsja' LANGUAGE 'c' IMMUTABLE STRICT;
振り仮名に変換して返す。
mecabと組み合わせて使うことが多いipadicの場合、特に指定しなければ /usr/local/lib/mecab/dic/ipadic 以下にインストールされる。そのため、本システムも、この場所にある辞書を参照するようになっている。
postgresql.confに、次のように記述することにより、参照する辞書の場所を変更できる。
tsja.mecab_dict_path = '/path/to/mecab/dict'
ipadicの文字コードは、特に指定しなければEUC-JPであるが、UTF-8にすることも可能である。検索対象の文字コードと一致している必要があるので、状況によっては、convert()などの函数を使って適宜対処しなければならない。
文字コードの一致をあえて検査せず、本システムを使う側に管理を委ねている。データベースを生成する際、(たとえばcreatedbの「--encode」オプションで) 符号化方式としてSQL_ASCIIを指定しておき、テーブルのtext型フィールドにUTF-8の文字列を格納する、といったことも可能だからである。
PostgreSQL付属ドキュメントの「全文検索」章、「テキスト検索のテストとデバッグ」節および「テキスト検索の制御」節にそって、検索函数の動作を示す。
ts_debug()でテキスト検索設定をテストできる。
SELECT * FROM ts_debug('japanese', 'PostgreSQLで日本語のテキスト検索ができます。'); alias | description | token | dictionaries | dictionary | lexemes --------+-------------+------------+-----------------+---------------+-------------- word | word | PostgreSQL | {snowball_dict} | snowball_dict | {postgresql} 助詞 | 助詞 | で | {japanese_dict} | japanese_dict | {} 名詞 | 名詞 | 日本語 | {japanese_dict} | japanese_dict | {日本語} 助詞 | 助詞 | の | {japanese_dict} | japanese_dict | {} 名詞 | 名詞 | テキスト | {japanese_dict} | japanese_dict | {テキスト} 名詞 | 名詞 | 検索 | {japanese_dict} | japanese_dict | {検索} 助詞 | 助詞 | が | {japanese_dict} | japanese_dict | {} 動詞 | 動詞 | でき | {japanese_dict} | japanese_dict | {できる} 助動詞 | 助動詞 | ます | {japanese_dict} | japanese_dict | {} 記号 | 記号 | 。 | {japanese_dict} | japanese_dict | {} (10 rows)
lexemesには自立語を正規化したものが挙がっており、非自立語 (ストップ・ワード) は除かれていることが分かる。また、英語部分はsnowball_dictで処理されている。
ts_parse()でテキストを解析し、トークン型を取得できる。
SELECT * FROM ts_parse('japanese', 'PostgreSQLで日本語のテキスト検索ができます。'); tokid | token -------+------------ 30 | PostgreSQL 211 | で 50 | 日本語 211 | の 50 | テキスト 50 | 検索 211 | が 60 | でき 212 | ます 221 | 。 (10 rows)
単語を渡すとその正規化形を取得できる。次に示す例ではたまたま「でき」を動詞と認識し、「できる」という終止形を返している。しかし実際には、前後も見なければ正しく認識できないことがある。そこでtsjaでは、形態素解析の結果を保存しておき、正規化の際に参照するようになっている。
SELECT ts_lexize('japanese_dict', 'でき'); ts_lexize ----------- {できる} (1 row)
テキスト文書を分析し、語彙素とその位置のリストであるtsvectorを返す。
SELECT to_tsvector('japanese', 'PostgreSQLで日本語のテキスト検索ができます。'); to_tsvector ------------------------------------------------------------ 'postgresql':1 'できる':8 'テキスト':5 '日本語':3 '検索':6 (1 row)
問い合わせをtsqueryに変換する。
SELECT to_tsquery('japanese', '日本語&検索'); to_tsquery ------------------- '日本語' & '検索' (1 row)
問い合わせに対する一致度を計測する。
SELECT ts_rank(to_tsvector('japanese', 'PostgreSQLで日本語のテキスト検索ができます。'), to_tsquery('japanese', '日本語&検索')); ts_rank ----------- 0.0973585 (1 row)
これはPostgreSQLにはじめから備わっているものである。文書中、問い合わせと一致した箇所を強調する。
SELECT ts_headline('japanese', 'PostgreSQLで日本語のテキスト検索ができます。', to_tsquery('japanese', '日本語&検索'), 'StartSel=<em>,StopSel=</em>'); ts_headline ---------------------------------------------------------------- PostgreSQLで<em>日本語</em>のテキスト<em>検索</em>ができます。 (1 row)
以上を組み合わせると、冒頭で紹介したような、テーブルのあるフィールドを対象とする全文検索ができる。
SELECT j_term, ts_headline('japanese', j_description, to_tsquery('japanese', '日本語&検索'), 'StartSel=<em>,StopSel=</em>'), ts_rank(to_tsvector('japanese', j_description), to_tsquery('japanese', '日本語&検索')) AS rank FROM terms_tbl WHERE to_tsvector('japanese', j_description) @@ to_tsquery('japanese', '日本語&検索') ORDER BY rank DESC ;
Copyright © 2015 KOYAMA Hiro <tac@amris.co.jp>