
- 導入製品:Oracle AI Database 26ai Enterprise Edition Release 23.26.1.0.0
Oracle AI Database/Oracle Databaseを構築、運用していると、「Oracle Text」という単語を見かけることがあると思います。
OracleのコンポーネントであるOracle Textは、Oracleが提供する高速の全文検索の機能です。
しかし、実際にインストールから利用までの手順は、あまり知られていないように思います。
本記事では、Oracle Textのインストールから利用、内部オブジェクト確認まで、Oracle Textについて一通りまとめました。
- Oracle Text のインストールされているか確認・インストール方法
- Oracle Text でしかできない検索方法
- Oracle Text 索引が自動生成する内部オブジェクト
Oracle Textのインストール
Oracle TextはOracle Databaseに標準で用意されている機能ですが、初期状態ではインストールされていないことがあります。
ここでは、インストール済みかどうかの確認から、未導入の場合のインストール、日本語設定までを順に進めます。
- インストールされているか確認
- catctx.sql を実行
- インストールされているか再確認
- 無効なオブジェクトの確認
- 言語設定
インストールされているか確認
まず、Oracle Textがインストールされているか確認します。
Oracle Text は内部的に「CONTEXT」というコンポーネント名で管理されているため、cdb_registry(データベースに登録されているコンポーネントの一覧)を参照します。
set pages 500
set lines 500
col comp_id for a10
col comp_name for a50
col version for a15
col status for a10
col schema for a10
col con_id for 99
SELECT comp_id,comp_name,version,status,control,con_id FROM cdb_registry WHERE comp_id = 'CONTEXT' ORDER BY con_id;上記のSQLの実行結果が、no rows selectedの場合は未インストールです。
既に行が表示され、STATUS列がVALIDになっていればインストール済みなので「無効なオブジェクトの確認」まで進んで問題ありません。
catctx.sql を実行
CDBとPDB$SEED、各 PDBにOracle Textをインストールします。
$ORACLE_HOME/perl/bin/perl $ORACLE_HOME/rdbms/admin/catcon.pl \
-e -u sys/<SYS_PASSWORD> -force_pdb_mode 'READ WRITE' \
-l /tmp -b catctx_install \
-d $ORACLE_HOME/ctx/admin/ catctx.sql --p'<CTXSYS_PASSWORD>' --p'SYSAUX' --p'TEMP' --p'NOLOCK'実行結果の末尾に、catcon.pl: completed successfullyと出力されたら、スクリプトは正常に実行されました。
[oracle@learnbytebybyte ~]$ $ORACLE_HOME/perl/bin/perl $ORACLE_HOME/rdbms/admin/catcon.pl \
-e -u sys/<SYS_PASSWORD> -force_pdb_mode 'READ WRITE' \
-l /tmp -b catctx_install \
-d $ORACLE_HOME/ctx/admin/ catctx.sql --p'<CTXSYS_PASSWORD>' --p'SYSAUX' --p'TEMP' --p'NOLOCK'
catcon::set_log_file_base_path: ALL catcon-related output will be written to [/tmp/catctx_install_catcon_450281.lst]
catcon::set_log_file_base_path: catcon: See [/tmp/catctx_install*.log] files for output generated by scripts
catcon::set_log_file_base_path: catcon: See [/tmp/catctx_install_*.lst] files for spool files, if any
catcon.pl: completed successfully実行後、-lオプションで指定したフォルダ内を確認すると複数のログが出力されています。
[oracle@learnbytebybyte ~]$ ls -la /tmp/catctx_install*
-rw-------. 1 oracle oinstall 5126461 Jun 18 10:19 /tmp/catctx_install0.log
-rw-------. 1 oracle oinstall 2563550 Jun 18 10:19 /tmp/catctx_install1.log
-rw-------. 1 oracle oinstall 419 Jun 18 10:15 /tmp/catctx_install_catcon_450281.lst19c以前での非コンテナデータベースにインストールする場合は、下記のコマンドの実行だけで問題ないです。
@?/ctx/admin/catctx.sql <CTXSYS_PASSWORD> SYSAUX TEMP NOLOCKインストールされているか再確認
インストールが正常に終了したら、再度同じコマンドで行が返ってくるはずです。
set pages 500
set lines 500
col comp_id for a10
col comp_name for a50
col version for a15
col status for a10
col schema for a10
col con_id for 99
SELECT comp_id,comp_name,version,status,control,con_id FROM cdb_registry WHERE comp_id = 'CONTEXT' ORDER BY con_id;STATUS列がVALIDになっていれば、Oracle Text は正常に利用できる状態です。
CON_IDが複数表示されているのは、コンテナごとにインストールされたことを表しています。
COMP_ID COMP_NAME VERSION STATUS CONTROL SCHEMA CON_ID
---------- --------------- ------------ -------- --------- -------- ------
CONTEXT Oracle Text 23.0.0.0.0 VALID SYS CTXSYS 1
CONTEXT Oracle Text 23.0.0.0.0 VALID SYS CTXSYS 3
無効なオブジェクトの確認
念のため無効なオブジェクトが無いことも確認しておきましょう。
インストール時に何らかの問題があると、CTXSYSスキーマのオブジェクトが INVALID(無効)になってしまいます。
-- CDB$ROOTとPDBでINVALIDオブジェクトがないことを確認
SELECT owner,object_name,object_type FROM dba_objects
WHERE owner = 'CTXSYS' AND status = 'INVALID';no rows selectedが出力されていたら問題ないです。行が返ってきた場合は、無効なオブジェクトが存在するので対処が必要です。
言語設定
Oracle Textの本体がインストールできたら、続いて言語のデフォルト設定を行います。
ここでは日本語で全文検索を行うため、dr0defin.sql にJAPANESEを指定して実行します。
$ORACLE_HOME/perl/bin/perl $ORACLE_HOME/rdbms/admin/catcon.pl \
-e -u sys/<SYS_PASSWORD> -force_pdb_mode 'READ WRITE' \
-l /tmp -b catctx_lang \
-d $ORACLE_HOME/ctx/admin/defaults/ dr0defin.sql --p'JAPANESE'日本語のテキストを扱う場合は、この設定を入れておくことで、後のトークン分割が日本語に適した挙動になります。
[oracle@learnbytebybyte ~]$ $ORACLE_HOME/perl/bin/perl $ORACLE_HOME/rdbms/admin/catcon.pl \
-e -u sys/<SYS_PASSWORD> -force_pdb_mode 'READ WRITE' \
-l /tmp -b catctx_lang \
-d $ORACLE_HOME/ctx/admin/defaults/ dr0defin.sql --p'JAPANESE'
catcon::set_log_file_base_path: ALL catcon-related output will be written to [/tmp/ccatctx_lang_catcon_452899.lst]
catcon::set_log_file_base_path: catcon: See [/tmp/catctx_lang*.log] files for output generated by scripts
catcon::set_log_file_base_path: catcon: See [/tmp/ccatctx_lang_*.lst] files for spool files, if any
catcon.pl: completed successfullyこちらも、インストール時と同様に、実行後は各ログにエラーが出力されていないかを確認します。
[oracle@learnbytebybyte ~]$ ls -la /tmp/catctx_lang*
-rw-------. 1 oracle oinstall 24177 Jun 18 10:38 /tmp/catctx_lang0.log
-rw-------. 1 oracle oinstall 12408 Jun 18 10:38 /tmp/catctx_lang1.log
-rw-------. 1 oracle oinstall 419 Jun 18 10:38 /tmp/catctx_lang_catcon_452899.lst
Oracle Textの準備
インストールが終わったら、実際に全文検索を試すための準備を行います。
ここでは検索用のユーザーを作成し、日本語レクサーの設定と、検証用のテストデータを用意します。
- 環境セットアップ
- 日本語レクサー(プリファレンス)の作成
- テスト用データの用意
環境セットアップ
まず、検索を行うユーザー textdemo を作成し、必要なロールと権限を付与します。
ALTER SESSION SET CONTAINER = ORCLPDB1;
-- ユーザ作成
CREATE USER textdemo IDENTIFIED BY <TEXTDEMO_PASSWORD>
QUOTA UNLIMITED ON USERS;
-- 必須ロールと権限
GRANT RESOURCE, CONNECT, CTXAPP TO textdemo;
-- CTXパッケージへの明示付与
GRANT EXECUTE ON CTXSYS.CTX_CLS TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_DDL TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_DOC TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_OUTPUT TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_QUERY TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_REPORT TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_THES TO textdemo;
GRANT EXECUTE ON CTXSYS.CTX_ULEXER TO textdemo;
日本語レクサー(プリファレンス)の作成
続いて、日本語のトークン分割を行うための設定を作成します。ここからは、作成したtextdemo ユーザーで接続して作業します。
connect textdemo/<TEXTDEMO_PASSWORD>@localhost:1521/orclpdb1
Connected.
EXEC CTX_DDL.CREATE_PREFERENCE('jp_lexer', 'JAPANESE_LEXER');CTX_DDL.CREATE_PREFERENCEには、「レクサー」と「プリファレンス」という2つの概念が登場します。混同しやすいので、切り分けて整理します。
レクサー(Lexer)とは、テキストを単語(トークン)に分割する処理を担う部品のことです。
Oracle Text は文書を索引化する際、内部でいくつかの処理段階を通しますが、そのうち「単語への切り出し」を受け持つのがレクサーです。
用途に応じて複数の種類が用意されており、代表的なものに次があります。
| レクサーの種類 | 用途 |
|---|---|
| BASIC_LEXER | 英語など、単語が空白で区切られる言語向け |
| JAPANESE_LEXER | 日本語向け(形態素解析による単語分割) |
| WORLD_LEXER | 言語を自動判別して分割 |
プリファレンス(Preference)とは、こうした部品に「どの種類を使い、どんな設定にするか」を決めて名前を付けた、再利用可能な設定オブジェクトです。
CREATE INDEX からはレクサーの種類を直接指定できず、必ずプリファレンス名を経由して参照します。
この関係を踏まえて、先ほどのコマンドを読み解くと次のようになります。
EXEC CTX_DDL.CREATE_PREFERENCE('jp_lexer', 'JAPANESE_LEXER');
-- ↑第1引数 ↑第2引数
-- プリファレンス 使用する
-- 名前 レクサーの種類レクサーの指定が重要
日本語は英語と違って単語が空白で区切られていないため、「完全ワイヤレスのイヤホン」という文字列を、そのままでは単語に分けられないのです。
トークン分割の結果は検索精度に直結するため、レクサーの選択は最も重要な設定のひとつです。
テスト用データの用意
Oracle Textの利用検証において、テスト用データを用意します。
connect textdemo/<TEXTDEMO_PASSWORD>@localhost:1521/orclpdb1
CREATE TABLE products (
id NUMBER PRIMARY KEY,
name VARCHAR2(200),
description VARCHAR2(2000),
price NUMBER,
category_id NUMBER,
in_stock NUMBER(1)
);
INSERT INTO products VALUES (1, 'ワイヤレスイヤホン Pro','完全ワイヤレスのイヤホン。ノイズキャンセリング搭載でクリアな音質。', 9800, 5, 1);
INSERT INTO products VALUES (2, '有線イヤホン ベーシック','コスパ重視の有線イヤホン。マイク付きで通話にも対応。', 1500, 5, 1);
INSERT INTO products VALUES (3, 'ワイヤレスヘッドホン','オーバーイヤー型のワイヤレスヘッドホン。長時間再生バッテリー。', 15800, 5, 1);
INSERT INTO products VALUES (4, 'Bluetooth スピーカー','防水ワイヤレススピーカー。アウトドアでも使える小型設計。', 6800, 6, 1);
INSERT INTO products VALUES (5, 'ゲーミングイヤホン','低遅延ワイヤレス接続のゲーミング向けイヤホン。', 12800, 5, 0);
INSERT INTO products VALUES (6, 'ワイヤレス特集モデル','ワイヤレス充電とワイヤレス接続に対応した完全ワイヤレスのワイヤレスイヤホン。', 7800, 5, 1);
COMMIT;
LIKE演算子とOracle Textの違い
まずはLIKEで検索してみます。「ワイヤレス」と「イヤホン」の両方を含み、在庫があり、1万円以下の商品を価格順に並べます。
set pages 500
set lines 500
col id for 9999
col name for a30
col description for a100
col price for 999999
SELECT id, name, description, price FROM products
WHERE description LIKE '%ワイヤレス%' AND description LIKE '%イヤホン%'
AND price <= 10000 AND in_stock = 1
ORDER BY price;結果は正しく返ってきます。件数が少なければ LIKE でも十分です。
ID NAME DESCRIPTION PRICE
----- ------------------------------ ---------------------------------------------------------------------------------------------------- -------
6 ワイヤレス特集モデル ワイヤレス充電とワイヤレス接続に対応した完全ワイヤレスのワイヤレスイヤホン。 7800
1 ワイヤレスイヤホン Pro 完全ワイヤレスのイヤホン。ノイズキャンセリング搭載でクリアな音質。 9800そこで、Oracle Text のCONTEXT索引を作成します。
CREATE INDEX idx_products_desc ON products(description)
INDEXTYPE IS CTXSYS.CONTEXT
FILTER BY category_id, price, in_stock
ORDER BY price
PARAMETERS ('LEXER jp_lexer SYNC (ON COMMIT)');各オプションの意味は次のとおりです。
FILTER BYに指定した列(category_id, price, in_stock)は、テキスト検索と同時に絞り込み条件として索引内で処理されます。ORDER BY priceを指定すると、価格順の並べ替えも索引内で完結します。SYNC (ON COMMIT)は、データを追加・更新してコミットしたタイミングで索引を自動的に同期する設定です。LEXER jp_lexerで、先ほど作成した日本語レクサーを指定しています。
CONTAINSは、作成した索引を活用して検索します。
SELECT id, name, description, price FROM products
WHERE CONTAINS(description, 'ワイヤレス AND イヤホン') > 0 AND price <= 10000 AND in_stock = 1
ORDER BY price;結果はLIKEと同じですが、内部の動きはまったく異なります。
ID NAME DESCRIPTION PRICE
----- ------------------------------ ---------------------------------------------------------------------------------------------------- -------
6 ワイヤレス特集モデル ワイヤレス充電とワイヤレス接続に対応した完全ワイヤレスのワイヤレスイヤホン。 7800
1 ワイヤレスイヤホン Pro 完全ワイヤレスのイヤホン。ノイズキャンセリング搭載でクリアな音質。 9800Oracle Textは、単語ごとに「その単語がどの行に出現するか」を記録した転置索引を作成します。
Oracle Textで実現できること
ここからは、LIKEでは実現できないOracle Textならではの検索を見ていきます。
- 関連度スコアを算出
- 近接検索(NEAR)
- 単語単位の高精度検索
関連度スコアを算出
CONTAINSの第3引数にラベル(ここでは 1)を指定すると、SCORE(1)でその検索単語の関連度スコアを算出します。
SELECT SCORE(1) AS relevance, id, name, description FROM products
WHERE CONTAINS(description, 'ワイヤレス', 1) > 0
ORDER BY SCORE(1) DESC;商品6が高スコア(13)で上位に表示されました。
RELEVANCE ID NAME DESCRIPTION
---------- ----- ------------------------------ ----------------------------------------------------------------------------------------------------
13 6 ワイヤレス特集モデル ワイヤレス充電とワイヤレス接続に対応した完全ワイヤレスのワイヤレスイヤホン。
3 3 ワイヤレスヘッドホン オーバーイヤー型のワイヤレスヘッドホン。長時間再生バッテリー。
3 5 ゲーミングイヤホン 低遅延ワイヤレス接続のゲーミング向けイヤホン。
3 1 ワイヤレスイヤホン Pro 完全ワイヤレスのイヤホン。ノイズキャンセリング搭載でクリアな音質。
3 4 Bluetooth スピーカー 防水ワイヤレススピーカー。アウトドアでも使える小型設計。これは、商品6の説明文に「ワイヤレス」が4回登場するためです。1回しか登場しない他の商品(スコア3)より、出現頻度が高いぶん関連度が高いと判定されています。
近接検索(NEAR)
NEARを使うと、複数の単語が近い位置にある文書だけを絞り込めます。
SELECT id, name, description
FROM products
WHERE CONTAINS(description, 'NEAR((ワイヤレス, イヤホン), 5)') > 0;「ワイヤレス」と「イヤホン」が5語以内に近接している商品だけが抽出されます。
ID NAME DESCRIPTION
---------- ----------------------- --------------------------------------------------
1 ワイヤレスイヤホン Pro 完全ワイヤレスのイヤホン。ノイズキャンセリング搭載でクリアな音質。
6 ワイヤレス特集モデル ワイヤレス充電とワイヤレス接続に対応した完全ワイヤレスのワイヤレスイヤホン。単に両方の語を含むだけでなく、「関連して使われている」文書を探したいときに使います。
単語単位の高精度検索
Oracle Textは文章を単語(トークン)に分割してから索引を作るため、単語単位での正確な検索ができます。
まずは、検証用の商品を2件追加します。
INSERT INTO products VALUES (7, '京都限定モデル','京都の職人が手がけた限定デザインのイヤホン。', 19800, 5, 1);
INSERT INTO products VALUES (8, '東京都発送モデル','東京都の倉庫から即日発送のワイヤレスイヤホン。', 8800, 5, 1);
COMMIT;LIKE で「京都」を検索すると、文字の並びで一致を判定するため、「東京都」という並びを含む「東京都発送モデル」まで出力されてしまいます。
日本語では、こうした意図しないヒットが頻繁に起こります。
SELECT id, name FROM products WHERE description LIKE '%京都%'; ID NAME
---------- --------------------
7 京都限定モデル
8 東京都発送モデル一方、Oracle Textで同じように「京都」を検索をすると次のようになります。
SELECT id, name FROM products WHERE CONTAINS(description, '京都') > 0;出力結果に「東京都発送モデル」はヒットしませんでした。
これは JAPANESE_LEXER が「東京都」を「東京」+「都」という単語に分割しているため、単語「京都」での検索では出力されません。
ID NAME
---------- --------------------
7 京都限定モデル
Oracle Text索引が自動生成する内部オブジェクトの確認
CREATE INDEX ... CONTEXTを実行すると、DR$<索引名>$... という命名の内部オブジェクトが自動生成されます。
これらは通常のオブジェクト一覧を眺めるだけでは、どれがどの索引に属するのか判別しづらいものです。
そこで、表を起点に関連オブジェクトを役割ラベル付きで一覧化するクエリを用意しました。DEFINE table_nameの表名を1か所変えるだけで、他の表にも使えます。
SET LINESIZE 200
SET PAGESIZE 100
DEFINE table_name = PRODUCTS
WITH text_idx AS (
SELECT idx_name FROM ctx_user_indexes WHERE idx_table = '&table_name'
)
-- 表本体
SELECT '&table_name' AS object_name, 'TABLE' AS object_type, '表本体' AS role FROM dual
UNION ALL
-- 表に直接付く索引
SELECT index_name, 'INDEX', '表に直接付く索引'
FROM user_indexes WHERE table_name = '&table_name'
UNION ALL
-- Text 内部の DR$ 表・索引
SELECT o.object_name, o.object_type, 'Text 内部オブジェクト'
FROM user_objects o, text_idx t
WHERE o.object_name LIKE 'DR$'||t.idx_name||'$%'
UNION ALL
-- 内部表にぶら下がる LOB セグメントと LOB 索引
SELECT l.segment_name, 'LOB', 'TOKEN_INFO の LOB 実体'
FROM user_lobs l, text_idx t
WHERE l.table_name LIKE 'DR$'||t.idx_name||'$%'
UNION ALL
SELECT l.index_name, 'LOB INDEX', 'LOB を引く索引'
FROM user_lobs l, text_idx t
WHERE l.table_name LIKE 'DR$'||t.idx_name||'$%'
UNION ALL
-- IOT 化された内部表($N/$S)の物理実体
SELECT i.index_name, 'IOT TOP', 'IOT の物理実体'
FROM user_indexes i, text_idx t
WHERE i.table_name LIKE 'DR$'||t.idx_name||'$%'
AND i.index_type = 'IOT - TOP'
ORDER BY object_type, object_name;OBJECT_NAME OBJECT_TYPE ROLE
---------------------------- --------------- ----------------------
DR$IDX_PRODUCTS_DESC$KD INDEX Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$KR INDEX Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$X INDEX Text 内部オブジェクト
IDX_PRODUCTS_DESC INDEX 表に直接付く索引
SYS_C008293 INDEX 表に直接付く索引
SYS_IOT_TOP_56910 IOT TOP IOT の物理実体
SYS_IOT_TOP_56917 IOT TOP IOT の物理実体
SYS_LOB0000056906C00006$$ LOB TOKEN_INFO の LOB 実体
SYS_IL0000056906C00006$$ LOB INDEX LOB を引く索引
DR$IDX_PRODUCTS_DESC$B TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$C TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$I TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$K TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$N TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$Q TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$S TABLE Text 内部オブジェクト
DR$IDX_PRODUCTS_DESC$U TABLE Text 内部オブジェクト
PRODUCTS TABLE 表本体
18行が選択されました。IDX_PRODUCTS_DESCは「表に直接付く索引」として表示されていますが、これは今回作成したCONTEXT索引そのもので、以下のDR$群を束ねる論理的な親にあたります。
| 接尾辞 | 種類 | ざっくりした役割 |
|---|---|---|
| $I | 表 | トークン表。単語→文書の対応を持つ転置索引の本体 |
| $X | 索引 | $Iを引くB-tree |
| SYS_LOB/SYS_IL | LOB/LOBの索引 | $Iの出現位置情報(TOKEN_INFO)の実体と索引 |
| $K/$KR/$KD | 表/索引 | 内部番号(DOCID)と実際の行(ROWID)の変換 |
| $S | 表 | FILTER BY/ORDER BY列の値を保持(絞り込み・並べ替え用) |
| $N | 表 | 削除・更新で無効になった文書の管理 |
| SYS_IOT_TOP_* | 索引 | $S/$N(IOT)の物理実体 |
| $B/$C/$Q/$U | 表 | 同期・更新まわりの管理用(メンテナンス基盤) |
まとめ
本記事では、Oracle Text のインストールから日本語全文検索の実装、内部オブジェクトの確認までを一通り実施しました。
- インストールされているか確認
- catctx.sql を実行
- インストールされているか再確認
- 無効なオブジェクトの確認
- 言語設定
Oracle Textを活用することで、LIKEより検索速度を保ちつつ、単語の出現頻度をスコア算出、近接検索、単語単位の高精度検索などを実現します。
Oracle Text は、外部の検索エンジンを導入せずとも SQL だけで本格的な全文検索を実現できるコンポーネントです。本記事が、その第一歩の助けになれば幸いです。
最後までご愛読ありがとうございました。
