« Winbind で完全に Samba と Active Directory を統合 | メイン | 基本スタンバイ・データベースを考える »

2008年04月16日

●Oracle Textで全文検索

以前PostgreSQLにおけるTsearch2を利用した全文検索を
試してみました。
Tsearch2で全文検索

今回はOracleの「Oracle Text」機能を試してみたいと思います。
ちなみにOracle 10gの「Oracle Text」はdbcaを使用した場合、
標準インストールされる機能です。
(Ora8ではConText、8iではinterMedia Textという名称で
オプション機能でした)

OracleVer:10.2.0.2
DBキャラクタセット:JA16SJISTILDE

dbcaでデータベースを作成した場合、「CTXSYS」ユーザーが
作成されているはずです。(DBA_USERS表で確認しましょう)

Oracle Textが有効であるかも確認してみましょう。

SQL> conn SYS/パスワード
SQL> SELECT COMP_NAME,STATUS,substr(VERSION,1,10) AS VER
  2 FROM DBA_REGISTRY
  3 WHERE COMP_ID = 'CONTEXT';

COMP_NAME STATUS VER
------------ ------ -----------
Oracle Text VALID 10.2.0.2.0


SQL>SELECT OBJECT_TYPE,OBJECT_NAME,STATUS
  2 FROM DBA_OBJECTS
  3 WHERE OWNER='CTXSYS' AND STATUS != 'VALID'
  4 ORDER BY OBJECT_TYPE,OBJECT_NAME;

レコードが選択されませんでした。

索引付けを行なう際の言語を、プリファレンスとして
作成します。プリファレンス名とレクサー(トークンを生成するアルゴリズム)を
引数に「ctx_ddl.create_preference」プロシージャを実行します。

SQL> conn ユーザー名/パスワード
SQL> execute ctx_ddl.create_preference('hoge_lexer', 'JAPANESE_LEXER');

「hoge_lexer」がプリファレンス名で、「JAPANESE_LEXER」が
指定する日本語レクサーです。
実行するユーザーには「CTXAPP」ロール権限が必要です。
権限が無い場合は付与してCTXSYSユーザーから付与して
あげましょう。

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

SQL> CREATE TABLE TBL (
  2 KEY NUMBER(2) PRIMARY KEY,
  3 ORIGTEXT VARCHAR2(100)
  4 );

Tsearch2と違い、Oracle Textでは分かち書きして
トークンを作成してくれるので、分かち書き用(インデックス用)の
列は不要です。

データをINSERTします。

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

索引を作ります。
INDEXTYPEに「CTXSYS.CONTEXT」を指定してテキスト索引であることを明示します。
PARAMETERSには、先に作成したプリファレンス名を指定します。

CREATE INDEX WAKACHI_INDEX ON TBL(ORIGTEXT)
INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS('lexer hoge_lexer');

お・・・。ちょい時間かかるな。

作成できたら実際に検索する前に、作成されたトークンを確認
してみましょう。
これは「DR$<インデックス名>$I」という索引表で確認できます。

SQL> SELECT TOKEN_TEXT FROM DR$WAKACHI_INDEX$I ORDER BY TOKEN_TEXT;

TOKEN_TEXT
---------------------------
1
15
1ST
5
MS
(略)
サイド
シャア
シャア
タイミング
(略)
赤い
戦艦
戦争
(略)

79行が選択されました。

よさげですね。

では実際に検索してみましょう。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'シャア')>0;

KEY ORIGTEXT
--- ------------------------
  1 ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!
  4 赤い色のMS!シャアじゃないのか?
  6 シャアが、MSに乗って輝いていたのは1stだけ。ms乗りとしては・・・

「シャア」を含むKey「1」「4」「6」が検索されました。
ここで注目したいのは、半角カナ「シャア」で登録したKey「6」も、
全角カナ「シャア」での検索にヒットすることです。
Oracleが「ゆれ」も解決してくれているわけですね。かしこい!

これはデータに全角/半角、大文字/小文字それぞれで登録した
「MS」(「MS」「ms」)についても同じことが言えます。
試してみてください。

また、Tsearch2で問題となった、トークンが分けられている複数の
単語を連結して検索する動作はどうなるのでしょう。
「ルウム戦役」というキーワードは「ルウム」と「戦役」に
分かち書きされるので、Tsearch2では「ルウム戦役」という
検索は工夫しないとできませんでした。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ルウム戦役')>0;

KEY ORIGTEXT
--- ------------------------
  1 ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!

すごい! Oracleかしこい! さすが商用RDBMS!

なお、一度索引を作成した場合、その後テキスト検索対象列に
追加/更新/削除が発生しても、トークンは同期されません。

SQL> INSERT INTO TBL VALUES(7,
  2 '私は父ジオン・ズム・ダイクンの元に召されるであろう!!!');
SQL> COMMIT;

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ジオン')>0;
レコードが選択されませんでした。

トークンを同期させるには
1.ctx_ddl.sync_indexプロシージャを実行
2.索引をREBUILD
する必要があります。

SQL> execute ctx_ddl.sync_index('WAKACHI_INDEX');
PL/SQLプロシージャが正常に完了しました。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ジオン')>0;

KEY ORIGTEXT
--- ------------------------
  7 私は父ジオン・ズム・ダイクンの元に召されるであろう!!!

他にCREATE INDEX文のオプションとして、「EVERY
「ON COMMIT」を指定することにより自動同期化も可能なようですが、
トークンの作成はコストが高い処理なので、業務時間中の実行には
注意が要りそうですね。
(夜間バッチがベターかな?)