Elasticsearchで全文検索する際にmatch
とmatch_phrase
の違いがはっきり身についていないのでまとめてみる。
version: Elasticsearch 7.5
matchクエリ
match
クエリを使うといわゆる曖昧検索ができる。match
クエリに渡した文字列はanalyzeされてから検索に使用される。
例えばtitleというフィールドに「2021年春に発売される新着本の特集!!NEW!!」という文字を保存していたとして、「2021春」で検索できるかどうかを考えてみる。
GET sample-index/_search
{
"query": {
"match": {
"title": "2021春"
}
}
}
解析されたクエリはORで検索される
indexをkuromojiで形態素解析されるように設定したので、「2021春」は「2021」と「春」に形態素解析される。
GET sample-index/_analyze
{
"field": "title",
"text": "2021春"
}
// 結果
{
"tokens": [
{ "token": "2021", 略},
{ "token": "春", 略}
]
}
クエリに渡すパラメータが複数の場合のデフォルトはORのため、「2021春」で検索する場合は「2021 OR 春」でマッチするかどうかを検索することになる。
つまり「2021年春に発売される新着本の特集!!NEW!!」は、2021と春の間に「年」が入っているものの検索されることになる。
ORで検索されるということは、「2021夏」でも検索に引っかかってくるということである。これが目的と異なる場合は、operator
をANDに変更してあげるとよい。
"match": {
"title": {
"query": "2021春",
"operator": "AND",
}
}
match_phraseクエリ
match
同様、match_phrase
クエリに渡した文字列はanalyzeされてから検索に使用される。
ただmatch_phrase
はmatch
よりもマッチする条件が厳しい。
全てのワードがクエリと同じ順序で、間に余計なものを含まず
全てのワードがクエリと同じ順序で含まれていなければいけない。
また各ワードの間に余計な文字が入ってもいけない。
「2021春」で検索しても、間に年が入っているためヒットしない。
GET sample-index/_search
{
"query": {
"match_phrase": {
"title": "2021春"
}
}
}
全てのワードがクエリと同じ順序で、さらに間に余計なものを含まず、というルールだと厳しすぎて曖昧検索というよりSQLのLIKE
に近しいイメージになっている。
ただSQLのLIKEとは異なり形態素解析を行ってくれるので、記号(・や!)を除いてマッチしてくれたり、大文字小文字を正規化してからマッチしてくれるので、多少は強力。
今回の例でいうと「特集new」が「特集!!NEW」にマッチしてくれる。
slop: 間に余計なものを含んでもいいように
とはいえ、実際にもう少し柔軟性を持たせたフレーズ検索をしたい場合にはslop
というオプションが使える。
"match_phrase": {
"title": {
"query": "2021春",
"slop": 1,
}
}
slop
は各検索クエリワードの間に何文字余計な単語の出現を許すか、というオプション。
今回は2021と春の間に「年」という余計な1単語(偶然1単語1文字になっているが、「発売」のように形態素解析後にこれ以上分割されないものを単位に考える)が入っているので、slop
に1以上の数字を指定すれば、検索にヒットするようになる。
multi_matchクエリ
複数のフィールドを対象に検索したい場合は、matchもmatch_phraseも両方ともmulti_match
を使用する。
type
multi_match
をmatchの複数フィールド版として使うのか、match_phraseの複数フィールド版として使うのかは、type
で指定する。
公式ドキュメントには複数のtypeが解説されているが、最低限best_fields
とphrase
の二つをしっかり理解すれば、大抵の検索目的は果たせそう。
type 説明 best_fields (default) Finds documents which match any field, but uses the _score from the best field. See best_fields. most_fields Finds documents which match any field and combines the _score from each field. See most_fields. cross_fields Treats fields with the same analyzer as though they were one big field. Looks for each word in any field. See cross_fields. phrase Runs a match_phrase query on each field and uses the _score from the best field. See phrase and phrase_prefix. phrase_prefix Runs a match_phrase_prefix query on each field and uses the _score from the best field. See phrase and phrase_prefix. bool_prefix Creates a match_bool_prefix query on each field and combines the _score from each field. See bool_prefix.
best_fields
multi_matchをmatchの複数フィールド版として使う場合は、typeをbest_fields
にする。
best_fields
の説明で公式ドキュメントに以下の通り記載されているが、スコアに関して注意が必要。
Normally the best_fields type uses the score of the single best matching field, but if tie_breaker is specified, then it calculates the score as follows:
the score from the best matching field plus tie_breaker * _score for all other matching fields
各フィールドのうち最もスコアの高いものが全体のスコアとして採用される。best_fields
という名前通りの挙動になっている。
もし各フィールドで計算されたスコアの合計点を全体のスコアとして採用したければ、tie_breaker
に1.0
を指定すればよい。(tie_breaker
のデフォルトは0.0
)
以下を計算式にすると、
the score from the best matching field plus tie_breaker * _score for all other matching fields
スコア =
最もマッチしたフィールドのスコア +
1.0 * 他のマッチしたフィールドその1のスコア +
1.0 * 他のマッチしたフィールドその2のスコア +
...
になるため、つまり全フィールドのスコアの合計になる。
phrase
公式ドキュメントに記載の通り、typeにphrase
を設定すればmatch_phrase相当の動きになる。
The phrase and phrase_prefix types behave just like best_fields, but they use a match_phrase or match_phrase_prefix query instead of a match query.
phrase
はbest_fields
のように動くと書いてあるが、スコア計算の仕方もbest_fields
と同様。そのため、目的次第ではtie_breaker
を同様に1.0
にする。
most_fields, cross_fields
best_fields
とtie_breaker
の組み合わせは、代わりにmost_fields
やcross_fields
を使用した方がいいのではないか、という点について記載する。
match_phraseの挙動をmulti_matchで実現するにはphrase
をtypeに設定する方法以外なく、most_fields
のmatch_phrase版などはなさそうだった。スコアを各フィールドの合計にするにはtie_breaker
の設定が必須のよう。
matchとmatch_phraseの複数フィールド版でのスコア合計方法を統一するために、most_fields
やcross_fields
でないと実現できない要件でない限りbest_fields
とtie_breaker
の組み合わせを利用するで問題ないと考えた。