[groonga-dev,01562] Re: 特殊記号混じりの前方一致検索について

Back to archive index

Kouhei Sutou kou****@clear*****
2013年 8月 1日 (木) 16:38:07 JST


須藤です。

遅くなってすみません。。。

In <63331****@web10*****>
  "[groonga-dev,01550] Re: 特殊記号混じりの前方一致検索について" on Sat, 27 Jul 2013 12:33:51 +0900 (JST),
  <mail_babir****@yahoo*****> wrote:

>> mroonga_escape()みたいな関数を提供して、
>> 
>>   SELECT ... WHERE
>>     MATCH (...) AGAINST (mroonga_escape("(仮)") IN BOOLEAN MODE);
>> 
>> というようにするのはどうでしょうか?これで「(仮)」そのものを
>> 検索するイメージです。(「(...)」をグループ化する書き方とし
>> て認識しない。)
...
> これは良さそうですね!
> 
> 単純な前方一致検索の用途で、()が含まれているせいでパースエラーが出ていることが結構あるので、便利だと思います。

よかったです!

> 場合によっては複数回エスケープ関数を使うことがありそうなので、関数名はe()くらい短くできるといいですね。

おぉ。。。それはやり過ぎな気持ちになりました。。。
MySQL全体でのエスケープ関数ならe()でもよさそうな気がしますが、
mroonga固有のエスケープ関数なのでe()はやり過ぎな気がする、と
感じました。


> ただ、また話を戻してしまって恐縮なのですが、
> 「+(仮)* -(笑)」のようなクエリで「(仮)が前方一致しつつ、(笑)は含まない」という検索条件をユーザが直接指定できるようにさせる場合、アプリケーション側では「半角スペースでワード分割して、前方一文字に+-を含む場合はそれを外し、後方一文字に*がある場合はそれも外したうえで、mroonga_escape()で括って、先ほど外した文字を付け直す」ということになるかと思います。
> 
> これはさすがに手順が煩雑なので、もう少し上手い方法を正規表現で書くなり、「¥¥」でのエスケープをすることになるとは思いますが。
> 
> ただ、「使いたい特殊文字が所定の位置にあることをアプリケーション側で特定して、それ以外の部分にある特殊文字だけをエスケープする」という処理は必要になってしまうかと思います。

はい、そうなると思います。

> これはアプリケーション側の責任、と言われてしまうと仕方がないのですが、myisamの場合、以前検証頂いた結果のように上記のクエリを直接mysqlに流しても正確に検索ができるんですね。

む、ちょっと待ってください。

データとして以下の2件が入った状態で、

  * (仮)テスト テスト1
  * 仮テスト テスト1

以下の検索結果になるのが前に確認した結果ですよね。

  * '(仮*'  -> 2件ヒット
  * '(仮)*' -> 1件ヒット(「(仮)テスト テスト1」がヒット)

私、「正確に検索」できている気がしていないので、もう少し調べ
てみました。以下のように3つのデータが入っている場合を考えま
す。↑にidが3のデータを追加しただけです。

mysql> SELECT * FROM `test`;
+----+-----------------------------+
| id | main                        |
+----+-----------------------------+
|  1 | (仮)テスト テスト1         |
|  2 | 仮テスト テスト1           |
|  3 | テスト1 仮                 |
+----+-----------------------------+
3 rows in set (0.00 sec)

この状態で「(仮)*」で検索します。正確に検索できていればid=1だ
けがヒットするはずですよね。しかし、以下のようにid=3もヒット
します。

mysql> SELECT * FROM `test` WHERE MATCH(`main`) AGAINST('(仮)*' IN BOOLEAN MODE);
+----+-----------------------------+
| id | main                        |
+----+-----------------------------+
|  1 | (仮)テスト テスト1         |
|  3 | テスト1 仮                 |
+----+-----------------------------+
2 rows in set (0.00 sec)

このことから、MyISAMは「()」を無視していると考えられます。試
しに「仮」だけで検索しても同じ結果になります。

mysql> SELECT * FROM `test` WHERE MATCH(`main`) AGAINST('仮' IN BOOLEAN MODE);+----+-----------------------------+
| id | main                        |
+----+-----------------------------+
|  1 | (仮)テスト テスト1         |
|  3 | テスト1 仮                 |
+----+-----------------------------+
2 rows in set (0.00 sec)

この結果から想像するに、「*」も無視しています。

さらに想像すると、「(仮)テスト テスト1」は

  「(仮)テスト」「テスト1」

ではなく、

  「仮」「テスト」「テスト1」

とトークナイズされているような気がします。試しに「テスト」で
検索するとid=1がヒットします。

mysql> SELECT * FROM `test` WHERE MATCH(`main`) AGAINST('テスト' IN BOOLEAN MODE);
+----+-----------------------------+
| id | main                        |
+----+-----------------------------+
|  1 | (仮)テスト テスト1         |
+----+-----------------------------+
1 row in set (0.00 sec)

このことから、MyISAMで「(仮)*」が正確に検索できているように
みえるのは実はそうではなく、単に「仮」で検索しているだけで、
「(仮)*」だと誤ヒットがありうるのではないかと考えられます。

> そして、mysql自体のマニュアルを確認しても()+-*~><などの特殊文字をエスケープするように指示している記述が見当たらないです。

はい、その通りです。

> この点から考えると、mysqlとしてはin boolean modeで有効になる特殊文字については、特段エスケープ処理をしなくても動作する、という想定になっているような気がするのですが、どうでしょうか。

前述の通り、そうではなく、特殊な文字を無視して検索しているよ
うに見えます。

> innodbでの挙動が、ft_min_word_len=1の場合でも前回テスト頂いた結果と同じならば、ストレージエンジンによって挙動が違うということで間違いないですが、仮にmyisamと同等の結果が得られるようになっている場合は、mroongaもその挙動に合わせられる方が望ましいと思います。

InnoDBの挙動を確認したかは忘れてしまったのですが、InnoDBはロ
グにエラーを出していたのでシンタックスエラーの場合はヒットし
ないと思います。なので、ストレージエンジンによって挙動が違う
というのが現状じゃないかという気がしています。。。

> あと、こじつけ的な気もしますが、mysqlで全文検索といえばmyisamという認識が一般的と思うので、その挙動に合わせる点では問題はないと思っています。
> 
> ・innodbが全文検索に対応したのがmysql5.6以降なので、そもそもmyisamの利用者が圧倒的多数と思われる
> ・もともと全文検索自体はmyisam固有の機能だったので、innodbよりmyisamに合わせる方が後方互換性があると言えそう
> ・myisamに合わせている限り、少なくとも想定外の検索結果になるということは考えにくい
> 
> なので、メリットはあってもデメリットは少ないと思うのです。
> (sennaからの移行やgroongaの実装との兼ね合いまで考えると話は変わってきそうですが)

なるほど。ただ、MyISAMで全文検索をしてきた人がどのくらいいる
のかわからないので、なんとも言えないなぁという気持ちがまだあ
ります。だれか、そのあたりの事情を教えてくれるとうれしいので
すが。。。


> 自動エスケープというよりは、特殊文字の解析方法をmyisamに合わせることができれば、より自然でいいかと思います。
> 
> myisamの実装を見て、似た実装ができそうなら引き続き検討頂けると有り難いです。

とすると、エラーの扱いまで含めたMyISAM完全互換のクエリー構文
パーサーを作ることになりますねぇ。構文エラーのときにエラー扱
いにできないと、パーサーを作るためのツールと相性が悪いので作
りづらいんですよね。。。うーん。。。

> mroonga_escape()については、あれば確実に便利な関数だと思うので、myisamの挙動の件とは別に実装して頂けると嬉しいです。
> (ユーザに特殊文字を使わせたくない場合に、予めその関数でラッピングしておくという使い方もできるので)

はい、ちょっと検討してみます。

> しつこく蒸し返して恐縮ですが、特殊文字の解析挙動が合えば、mroongaの導入にあたっての障壁がより低くなることは間違いないと思う(どうやってエスケープしようという点で悩まなくて済むし、後になってエスケープが必要なことを知って慌てることもない)ので、実現可能であれば、ぜひお願いしたいと思います。

は、はい。。。
うーん、私は、ユーザーとしては「特殊文字が勝手に無視されてエ
ラーにならずに動いているけど期待した挙動じゃないこともある
(今のMyISAMの挙動)」よりは、「エラーになってすぐに問題がわ
かる(InnoDBのログをみる場合や今のmroongaの挙動)」というタ
イプなので、なかなか踏ん切りがつかないんですよねぇ。。。

-- 
須藤 功平 <kou****@clear*****>
株式会社クリアコード <http://www.clear-code.com/> (03-6231-7270)

groongaサポート:
  http://groonga.org/ja/support/
パッチ採用はじめました:
  http://www.clear-code.com/recruitment/
コミットへのコメントサービスはじめました:
  http://www.clear-code.com/services/commit-comment.html



groonga-dev メーリングリストの案内
Back to archive index