この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
Oracleに標準で備わっている日本語検索機能をRailsから使ってみましょう
データベース内のテキストから特定の語句が含まれるレコードを検索する必要がある場合、通常は弊社の提供する AdvantageSearch というサービスをお薦めしています。
しかし、OracleにはOracle Textというテキスト検索のための仕組みが標準で備わっているので、今回はそちらを使って検索機能を実装してみます。
https://blogs.oracle.com/oracle4engineer/oracle-text-v5
Oracle Textとは
Oracle Database本体に標準でバンドルされている、テキスト検索機能です。
欧文に対応したBasic Lexserのほかに、JAPANESE_VGRAM_LEXERと呼ばれる日本語に特化したトークン処理のエンジンを持っており、分かち書きのない日本語テキストに対して高速な部分一致検索を行うことができます。
V-GRAMとは
2文字単位で文字を区切っていくBIGRAM(2-GRAM)を、日本語の特性に合わせて拡張したアルゴリズムです。文頭に置いてはいけない禁則文字がトークンの先頭にこないように、トークンの長さを基本の2文字から伸ばしていくことから 可変gram という意味でV-GRAMと名付けられたようです。
詳しくはこちらが参考になります。
http://otndnld.oracle.co.jp/products/oracle8i/intermedia/htdocs/imt.htm
Railsからの利用
下準備
下準備として、DBユーザーに権限を付与したり、JAPANESE_VGRAM_LEXERのプレファレンスを作成したりする必要があります。こちらを参考に各自の環境に合わせて作業して下さい。
https://blogs.oracle.com/oracle4engineer/oracle-text-v5
マイグレーション
マイグレーションファイルを作成して、検索を提供したい VARCHAR2型, CLOB型のカラムにOracle Text用のインデックス(Context Index)を付与します。
add_index :users, :address, options: "INDEXTYPE IS ctxsys.context PARAMETERS ('lexer jp_lexer sync (on commit)')"
ここでは、前述の下準備で作成したプレファレンスに jp_lexer
という名前を使用しています。
また、sync (on commit)
というオプションによって、データベースにコミットした時点でインデックスの更新を行うように指定します。通常のインデックスとくらべて更新コストが重いため、文字量が多いカラムの場合は別のタイミングを選択する必要があるかも知れません。
今回使用しているOracle enhanced adapterには、もともとContext Indexを作成する add_context_index というメソッドが用意されているのですが、デフォルト以外のレクサーを指定することができないため、 add_index
にオプションを渡すという方法をとっています。
ActiveRecordからの検索
Oracle TextLは独自の CONTAINS()
という関数を使って検索するため、Oracle enhanced adapterには専用のメソッドが用意されてます。
class User
has_context_index
end
この has_context_index
というクラスマクロを記述することで contains
というクラスメソッドがミックスインされ、
User.contains(:address, '大井町').order(:created_at).limit(10)
といった検索ができるようになります。
ただしこのメソッドには、別のテーブルと joins
して検索したときにカラム名が重複していると正常に検索できないというバグがあるため、例えば
Corporation.joins(:customers).merge(Customer.contains("customers.address", '大井町'))
のように、contains
メソッドにテーブル名も含めた名前を文字列で渡してあげる必要があります。
CONTAINS関数の注意点
CONTAINS()
の検索式の中で使える独自の文法や予約語があるため、一般ユーザーの入力をそのまま渡すとSQLエラーになる場合があります。
意図して複雑な検索式を使用しない場合は、下記のドキュメントに記載されている予約語を {
と }
でエスケープするような処理が必要です。
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cqspcl.htm
日本語以外の検索について
JAPANESE_VGRAM_LEXER を使ったインデックスでは、いわゆる英数文字で書かれたテキストは、空白区切りでトークンとして分解されるため、「目的の単語が含まれるレコードを検索する」という動作になります。
hogehoge fugafuga
という文字列が格納されたレコードにヒットする検索語は hogehoge
と fugafuga
だけで、 hoge
や fuga
ではヒットしないことに注意して下さい。
まとめ
RailsからOracle Textを使ったテキスト検索機能を利用する方法について紹介しました。
AdvantageSearch にはかないませんが、なかなか便利な検索エンジンですのでみなさんも利用してみて下さい。