Mroongaで全文検索以外でGroongaのノーマライザーを使う方法

[Groonga Advent Calendar 2015](http://qiita.com/advent-calendar/2015/groonga)の18日目の記事です。

[こちらの記事](http://qiita.com/naoa/items/cf8b5b8e17806af3aae3)では全文検索以外の一致検索ではGroongaのカスタムノーマライザー使えなくて困りましたねということを記載しました。

今回はその解決方法の例を紹介します。
## mroonga_normalize

mroonga_normalizeというUDFを作成してマージしてもらいました。たぶん、Mroonga5.11から利用できるとおもいます。

これは文字列をGroongaのノーマライザーを使って正規化する関数です。

```sql
DROP TABLE tags;
CREATE TABLE IF NOT EXISTS tags (
  tag varchar(255) NOT NULL,
  text varchar(1000) NOT NULL,
 FULLTEXT KEY text (text) COMMENT 'normalizer "NormalizerAuto"'
) ENGINE=Mroonga DEFAULT CHARSET=utf8;
INSERT INTO tags VALUES("Hoge", "㍑");
```

```sql
mysql> SELECT * FROM tags WHERE mroonga_normalize(text) = mroonga_normalize("リットル");
+------+------+
| tag  | text |
+------+------+
| Hoge | ㍑  |
+------+------+
1 row in set (0.01 sec)
mysql> SELECT * FROM tags WHERE mroonga_normalize(text) = mroonga_normalize("㍑");
+------+------+
| tag  | text |
+------+------+
| Hoge | ㍑  |
+------+------+
1 row in set (0.01 sec)
```

このように出力させるカラムと検索クエリの両方にmroonga_normalize()を使えばGroongaのノーラマライザーを使って一致させることができます。

なお、mroonga_normalize()は、自動的にカラムのノーマライザーを調べて使ってくれるということをしてくれません(たぶんUDFの世界ではMySQLのフィールドにアクセスできず判断できない)。

そのため、NormalizerAuto以外のノーマライザーを使う場合は以下のように明示的にノーマライザー名を指定する必要があります。

以下は``NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark``を使う例です。

```sql
DROP TABLE tags;
CREATE TABLE IF NOT EXISTS tags (
  tag varchar(255) NOT NULL,
  text varchar(1000) NOT NULL,
 FULLTEXT KEY text (text) COMMENT 'normalizer "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark"'
) ENGINE=Mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO tags VALUES("Hoge", "ふらつく");
INSERT INTO tags VALUES("Hoge2", "ブラック");
```

```sql
mysql> SELECT * FROM tags WHERE text = "ブラック";
+-------+--------------+
| tag   | text         |
+-------+--------------+
| Hoge  | ふらつく |
| Hoge2 | ブラック |
+-------+--------------+
mysql> SELECT * FROM tags WHERE mroonga_normalize(text, "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark") = mroonga_normalize("ブラック", "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark");
+-------+--------------+
| tag   | text         |
+-------+--------------+
| Hoge2 | ブラック |
+-------+--------------+
1 row in set (0.01 sec)
```

``NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark``はutf8_unicode_ciのようにひらがなとカタカナは同一視させつつ、ちっちゃい文字（っとか)、濁音有無の同一視を除外させたノーマライザーです。ふらつくとブラックが一致しないようにしたりできます。

## 他のストレージエンジンでも利用可

mroonga_normalizeはテーブルのストレージエンジンに依存せず、InnoDBやMyISAMのテーブルでも単なるSELECTだけでも利用できます。

カジュアルにMySQLサイドだけで以下のような文字種変換をしたいときにも使えるかもしれません。

* アルファベットを半角小文字アルファベットに統一したい
  * NormalizerAuto
* 全角半角は区別して小文字アルファベットを大文字アルファベットに統一したい
  * NormalizerMySQLGeneralCI
* アルファベットを半角大文字アルファベットに統一したい
  * NormalizerMySQLUnicodeCI
* カタカナをひらがなに統一したい
  * NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark

```sql
mysql> SELECT mroonga_normalize("ＡｂCdあイ㍑", "NormalizerAuto");
+----------------------------------------------------------+
| mroonga_normalize("ＡｂCdあイ㍑", "NormalizerAuto") |
+----------------------------------------------------------+
| abcdあイリットル                                   |
+----------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT mroonga_normalize("ＡｂCdあイ㍑", "NormalizerMySQLGeneralCI");
+--------------------------------------------------------------------+
| mroonga_normalize("ＡｂCdあイ㍑", "NormalizerMySQLGeneralCI") |
+--------------------------------------------------------------------+
| ＡＢCDあイ㍑                                                  |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT mroonga_normalize("ＡｂCdあイ㍑", "NormalizerMySQLUnicodeCI");
+--------------------------------------------------------------------+
| mroonga_normalize("ＡｂCdあイ㍑", "NormalizerMySQLUnicodeCI") |
+--------------------------------------------------------------------+
| ABCDあい㍑                                                      |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT mroonga_normalize("ブラック", "NormalizerMySQLUnicodeCI");
+---------------------------------------------------------------+
| mroonga_normalize("ブラック", "NormalizerMySQLUnicodeCI") |
+---------------------------------------------------------------+
| ふらつく                                                  |
+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT mroonga_normalize("ブラック", "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark");
+--------------------------------------------------------------------------------------------------+
| mroonga_normalize("ブラック", "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark") |
+--------------------------------------------------------------------------------------------------+
| ぶらっく                                                                                     |
+--------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```

## インデックスを使ってGroongaのノーマライザーで高速に一致検索したい場合
なお、mroonga_normalizeでは、インデックスを使った検索を一致させることはできません。

どうしてもインデックスを使いたい場合、以下のようにシステム変数``mroonga_boolean_mode_syntax_flags``に``ALLOW_COLUMN``を設定し、AGAINST句にGroongaの[クエリー構文](http://groonga.org/ja/docs/reference/grn_expr/query_syntax.html)を使えば実現できます。

```sql
DROP TABLE tags;

CREATE TABLE IF NOT EXISTS tags (
  tag varchar(255) NOT NULL,
  text varchar(1000) NOT NULL,
  text2 varchar(1000) NOT NULL,
 KEY text (text) COMMENT 'normalizer "NormalizerAuto"',
 FULLTEXT KEY text2 (text2) COMMENT 'normalizer "NormalizerAuto"'
) ENGINE=Mroonga DEFAULT CHARSET=utf8;
INSERT INTO tags VALUES("Hoge", "㍑", "");

SET mroonga_boolean_mode_syntax_flags = "SYNTAX_QUERY,ALLOW_LEADING_NOT,ALLOW_COLUMN";

SELECT * FROM tags WHERE MATCH(text2) AGAINST("text:リットル" IN BOOLEAN MODE);
```
``ALLOW_COLUMN``は``[カラム名]:[検索クエリ]``という構文を許すというフラグです。これを使うとMATCH句に指定したカラム以外でもGroongaのクエリー構文をフルで利用することができます。

ちなみにMySQLでは１つのクエリーにつき１つのインデックスしか使えませんが、Groongaのクエリー構文で記載することにより、複数のインデックスでかなり高速に絞込ができて嬉しいという使い方もあります。

参考：[MroongaによりMySQLで複数インデックスを有効に全文検索する方法](http://createfield.com/Mroonga%E3%81%AB%E3%82%88%E3%82%8AMySQL%E3%81%A7%E8%A4%87%E6%95%B0%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)