隔週連載groonga

第10回 [実録] MySQL向け全文検索エンジン「Tritonn」から「mroonga」への移行ガイド(3)

この記事を読むのに必要な時間:およそ 9 分

Tritonnからmroongaへの全文検索クエリ書き換えガイド

Tritonnのユーザガイドにてまとめられている検索クエリは基本的にmroongaでもそのまま使えます。ただ一部異なる点があるため,mroongaではどうクエリを書き換える必要があるのか,順を追って紹介します。

基本的な検索

このパターンのクエリは完全互換があり,そのまま使えます。

SELECT * FROM tbl WHERE MATCH(col) AGAINST("検索キーワード");
boolean modeの利用(演算子の利用)

Tritonnと同様に,mroongaも次のような検索時に使うboolean modeをサポートしています。

AND検索

キーワードAを含み,かつキーワードBを含む文書の検索

OR検索

キーワードAまたはキーワードBを含む文書の検索

AGAINST句の括弧の末尾にIN BOOLEAN MODEとつけることで演算子が利用できるようになります。例えば「東京」「神奈川」を含み,⁠埼玉」を含まない文書を検索する場合には次のようなSELECT文になります。

SELECT * FROM t1 WHERE MATCH(document) AGAINST("+東京 +神奈川 -埼玉" IN BOOLEAN MODE);

Tritonnでは解釈のできない演算子や不正バイト文字列が渡された場合には,それ以降の文字列を無視していました。しかしmroongaではその処理を厳格に行うため,演算子に使われる()~+><-*記号を含む文字列を検索すると次のようなエラーが起きます。

ERROR 1064 (42000): failed to parse fulltext search keyword: <<<foo>>>: <Syntax error! (<foo>)>

演算子に使われる記号を含む検索を行う方法を紹介します。ポイントとしては,AGAINST句のシングルクォートの中で,ダブルクォートでワードを囲むということです。

-- mroongaで<foo>という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+<foo>" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+'<foo>'" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+\"<foo>\"" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"<foo>"' IN BOOLEAN MODE);

さらに,ダブルクォートを含む検索を行う場合にはこのように2重でエスケープする(⁠\をもう一つ追加する)必要があります。理由としては,MySQLで解釈された後にgroongaへ渡されるという2回の処理を挟むためです。

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+"foo"" IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+'\"foo\"'" IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"\"foo\""' IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"\\"foo\\""' IN BOOLEAN MODE);

律儀にエスケープ処理する実装もできますが,業務要件によっては検索キーワードにこれらの演算子がある場合には取り除いた上で検索する処理としても良いでしょう。

なお,MySQL 5.0のTritonnでもダブルクォートでワードを囲った検索も使えます。しかし制御文字や不正なバイト文字列が渡された場合にはTritonnが動くMySQLが落ちる不具合があるため,入力文字のフィルタリングを適宜行いましょう。

mroongaのプラグマ・演算子対応状況

TritonnではSennaのクエリーが使えますが,mroongaではそのすべてに対応しているわけではありません。2013年8月執筆時点のプラグマ及び演算子対応状況は次の通りです。

プラグマの対応状況

Dプラグマ・Wプラグマに対応しています。Eプラグマには対応していません。例えば演算子の既定値(演算子を省略した場合にどの演算を行うか)を指定するDプラグマは次のように使います。

-- 東京 と アジア料理 を共に含むレコードを検索(Tritonn)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("*D+ 東京 アジア料理" IN BOOLEAN MODE);

-- 東京 と アジア料理 を共に含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*D+ 東京 アジア料理' IN BOOLEAN MODE);

演算子を含むワードの検索をする場合には次のようなクエリになります。検索語をダブルクォートで囲う必要があるため,シングルクォートの中でダブルクォートを使います。

-- 東京 と <アジア料理> を共に含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*D+ 東京 "<アジア料理>"' IN BOOLEAN MODE);

OR検索は次のように使います。

-- 東京 または アジア料理 を含むレコードを検索(Tritonn)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("*DOR 東京 アジア料理" IN BOOLEAN MODE);

-- 東京 または アジア料理 を含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*DOR 東京 アジア料理' IN BOOLEAN MODE);
演算子の対応状況

10種類の演算子のうち,主要な6つの演算子に対応しています。

  • +単語
  • -単語
  • OR 単語
  • (単語・演算子群)
  • 単語*
  • "単語"

ただし,次の演算子には対応していません。

  • ~単語
  • <単語 または >単語
  • *S[数値]"文字列"
  • *N[数値]"文字列"
kwic関数

Tritonnのkwic関数は,mroongaではmroonga_snippetで代替します。詳細はmroonga公式ドキュメントのスニペットの取得方法 (文脈付き索引)が参考になります。

2ind機能(2インデックス同時使用機能)

Tritonnでは不要なデータアクセスを省略するための2indパッチが提供されていました。mroongaではこれが標準となり,必要最小限のコストで行カウントの結果を返すことができます。詳細はmroonga公式ドキュメントの3.3.1.10. 行カウント高速化が参考になります。

著者プロフィール

吉田健太郎(Kentaro Yoshida)

株式会社リブセンス,Web系インフラの研究開発エンジニア。

ITベンチャー立ち上げに参画し,幅広い領域での経験を積むこと8年目。フルスタックエンジニアを目指して,湧き出るアイディアを形にする日々を過ごしている。

GitHub:https://github.com/y-ken/
ブログ:http://y-ken.hatenablog.com/