※頂いたコメントを元に改訂予定です。必ずコメントまで読んで下さい (2020-12-01)
Mroongaの文字列正規化はオプション豊富な NFKC100
があって、いい感じの手広く当てにいく検索インデックスを作れます。
MroongaのNormalizer/Tokenizer/TokenFilter 2020-02-25
以前の記事で採用した例
- 長音記号を正規化
- 中点を"·" (U+00B7 MIDDLE DOT)に正規化
- "ヴァヴィヴヴェヴォ"を"バビブベボ"に正規化
- 全角ひらがな、全角カタカナ、半角カタカナの文字を同一視
- かなカナの小さな文字を大きな文字と同一視
- ハイフンを"-" (U+002D HYPHEN-MINUS)に正規化
Mroonga には UDF として mroonga_snippet_html(document, key_word)
が実装されていて、簡単にGoogleみたいな検索結果画面をつくれます。
※「実験的機能」の但し書きつき
document
の中から key_word
を含む周辺のテキストを切り取って、 key_word
部分を強調表示のmark-upをつけて返してくれます。
しかし、mroonga_snippet_html
の中で document
中の key_word
を探すときにはデフォールトのアルファベット大小同一程度の正規化処理になっているようで、インデックス作成時に NFKC100
を使っていると空振りすることがあります。
「バイオリン」でdocument中の「ヴァイオリン」にヒットしたのに、snippet が出てこないとかです。
Mroonga v10.07 (2020-10-02)まで
インデクシング時に NFKC100
で凝った正規化をしたなら、 mroonga_snippet_html
の引数にはどちらも、同様の正規化変換をした文字列を与える必要があります。
同じく UDF で mroonga_normalize(string, normalizer_name)
があるのですが、これに NFKC100
のオプションをつけて実行する方法がわかりませんでした。
FULLTEXT INDEX
につけてるのはこんな感じですから。
normalizer "NormalizerNFKC100(
\'unify_prolonged_sound_mark\', true,
\'unify_middle_dot\', true,
\'unify_katakana_v_sounds\', true)"
,
token_filters "TokenFilterNFKC100(
\'unify_kana\', true,
\'unify_kana_case\', true,
\'unify_hyphen\', true)"
そういうときには、一段抽象化を下げて、汎用の mroonga_command
で groonga command を使います。
SELECT mroonga_command('normalize \'NormalizerNFKC100(
"unify_prolonged_sound_mark", true,
"unify_middle_dot", true,
"unify_katakana_v_sounds", true)\' "ヴァイオリン"');
{"normalized":"バイオリン","types":[],"checks":[]}
JSONで返ってくるので、SQLの中での取り回しがちょっと難しいです。ホスト言語(ここの場合はJava)で取り回すことにします。
// NormalizeオプションはFULLTEXT INDEXの定義に合わせること
private static final String MROONGA_NORMALIZER = "normalize 'NormalizerNFKC100(" +
"\"unify_prolonged_sound_mark\", true," +
"\"unify_middle_dot\", true, " +
"\"unify_katakana_v_sounds\", true)' \"";
private static final String MROONGA_TOKENFILTER = "normalize 'NormalizerNFKC100(" +
"\"unify_kana\", true, " +
"\"unify_kana_case\", true, " +
"\"unify_hyphen\", true)' \"";
...
String words = condition.getFulltextExpression();
StringBuilder command = new StringBuilder();
command.append(MROONGA_NORMALIZER).append(words).append("\"");
String normalizedQuery = mapper.mroongaCommand(command.toString());
command.setLength(0);
command.append(MROONGA_TOKENFILTER).append(normalizedQuery).append("\"");
String filteredQuery = mapper.mroongaCommand(command.toString());
String originalText = mapper.getTextByFulltextId(docInfo.getFullTextId());
command.setLength(0);
command.append(MROONGA_NORMALIZER).append(originalText).append("\"");
String normalizedText = mapper.mroongaCommand(command.toString());
command.setLength(0);
command.append(MROONGA_TOKENFILTER).append(normalizedText).append("\"");
String filteredText = mapper.mroongaCommand(command.toString());
...
正規化オプションの適用順序については、ソースまで読まないといけないのかもしれません。
"ヴァヴィヴヴェヴォ"を変換してから、unify_kana変換することが確実になるように、2段変換しています。
Mapper中のSQL
SELECT mroonga_command(#{command});
...
SELECT mroonga_snippet_html(#{text}, #{keyword});
Mroonga v10.09 (2020-12-02)以降
2020-12-26に追記
@ktou さんから新版 v10.09 を勧めるコメントもらっていながら、他事に紛れてなかなか確認できませんでしたが、ようやく v10.09 でのありがたい新機能を確認しました。
@ktou
次のMroongaからはmroonga_snippet_html(column, 'diaries' as table_name, 'content' as index_name, '...' as query)で指定したインデックスに設定されているノーマライザーの情報を使ってくれるようになります。
更新前の状態
2.5.18. CentOS 7(MariaDB 10.4のパッケージを利用) でインストールした9.12でした。
MariaDB [(none)]> SHOW VARIABLES LIKE 'mroonga_version';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| mroonga_version | 9.12 |
+-----------------+-------+
yum update
$ yum info --enablerepo=epel mariadb-10.4-mroonga
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
196 packages excluded due to repository priority protections
Installed Packages
Name : mariadb-10.4-mroonga
Arch : x86_64
Version : 9.12
Release : 1.el7
Size : 534 k
Repo : installed
...
Available Packages
Name : mariadb-10.4-mroonga
Arch : x86_64
Version : 10.09
...
$ sudo yum update --enablerepo=epel mariadb-10.4-mroonga
...
Updated:
mariadb-10.4-mroonga.x86_64 0:10.09-1.el7
Dependency Updated:
MariaDB-client.x86_64 0:10.4.17-1.el7.centos
MariaDB-server.x86_64 0:10.4.17-1.el7.centos
Complete!
$ sudo systemctl daemon-reload
$ sudo mysql < /usr/share/mroonga/install.sql
$ sudo mysql
MariaDB [(none)]> SHOW VARIABLES LIKE 'mroonga_version'\G
*************************** 1. row ***************************
Variable_name: mroonga_version
Value: 10.09
### Normalize
> @ktou
ただ、unify_kanaとunify_katakana_v_soundsは一緒には使えない(unify_kanaでカタカナがすべてひらがなになってしまいunify_katakana_v_sounds対象の文字がなくなってしまう)ので、unify_kanaをunify_to_katakanaにしてください。これで、ノーマライザーとトークンフィルターにわけなくてもよくなります。
```sql
ALTER TABLE t_content ADD FULLTEXT INDEX ftx_ngram(t_ngram) COMMENT
'tokenizer "TokenBigram",
normalizer "NormalizerNFKC100(
\'unify_prolonged_sound_mark\', true,
\'unify_middle_dot\', true,
\'unify_katakana_v_sounds\', true,
\'unify_to_katakana\', true,
\'unify_kana_case\', true,
\'unify_hyphen\', true)"'
;
normalizer と token_filters の2段構えにしたのは「よみがな検索」もやってみたくて、TokenMecab
を使おうとしていたためです。
カタカナとひらがなをどちらかに統制してしまうと、TokenMecab
のような形態素解析は外来語や外来固有名詞の区切りを文字種変化に頼っているので悲惨なことになります。normalizer - Mecab - token_filters の順で加工する必要があります。今回の更新で mroonga_snippet_html
が参照するのは normalizer だけみたいなので、 TokenMecab
のときにかなカナ同一視をしたいなら、前節の後付けのsnippet補完処理が依然として必要です。
TokenBigram
ならその心配はないので、normalizer に一本化します。
SQL
snippetCommand = " mroonga_snippet_html(tc.t_ngram, 't_content' as table_name, 'ftx_ngram' as index_name, '" + words + "' as query) ";
10.09のmroonga_snippet_html
のメリット
- FULLTEXT INDEXで設定した Normalizeオプションを完全再現した snippet_html取得になる
- 多数あるオプションを引き渡すのが10.07以前は難しかった
- SQLひとつで検索結果とヒットした近くのテキスト(snippet_html)を同時に得られるようになった
- 後付けのsnippet補完処理が不要になった
- SQL request数が最小で済む
- snippet_html内容が元テキストのまま
- 後付けの補完処理では、 noramlize変換したテキスト上でのnormalize変換した検索語が強調される
- カナかな同一視の normalizeをかけると日本語テキストして不自然だった
mroonga_snippet_html() 使うにあたって 10.09 への update を強く勧めてもらえて助かりました。