Carpe Diem

備忘録

Multi Match Queryのtypeの違い

概要

以前Bool Query と Dis Max Query の違いについて書きました。
今回はその中で出てきたMulti Match Queryのtypeの違いについて書きます。
どれも複数のフィールドに対して実行するクエリですが、それぞれ用途が異なります。

環境

  • Elasticsearch 6.6.0

データ投入

curl -s -H "Content-Type: application/json" \
  -XPOST localhost:9200/my_index/my_type/_bulk -d '
{"index": {"_id": "1"}}
{"title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen."}
{"index": {"_id": "2"}}
{"title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis."}
{"index": {"_id": "3"}}
{"first_name": "Will", "last_name": "Smith"}
{"index": {"_id": "4"}}
{"first_name": "Smith", "last_name": "Jones"}
'

Multi Match Query

種類

主に以下の3つを使い分けると思います。

type 説明 使いドコロ
best_fields 各フィールドに対して検索し、最も一致するフィールドのスコアを返す 複数のフィールドからある特定のフレーズとマッチしたものが欲しい時
most_fields 各フィールドに対して検索し、それぞれのスコアを加算したスコアを返す 複数のフィールドを横断して検索したい時
cross_fields 複数のフィールドを結合して1つのフィールドかのように検索できる 姓名、読みがな、など1つにまとめたいがフィールドが分かれている時

best_fields

クエリ

best_fieldsdis_maxとしてラップされるので

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "brown fox",
      "type": "best_fields",
      "fields": ["title", "body"]
    }
  },
  "size": 5,
  "from": 0
}
'

というクエリは

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "brown fox" } },
                { "match": { "body": "brown fox" } }
            ]
        }
    },
    "size": 5,
    "from": 0
}
'

として実行されます。

結果

{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "Keeping pets healthy",
          "body" : "My quick brown fox eats rabbits on a regular basis."
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "Quick brown rabbits",
          "body" : "Brown rabbits are commonly seen."
        }
      }
    ]
  }
}

dis_maxなのでマッチ率が高いフィールドを持つドキュメントを上位に持ってきてくれますね。

operatorにandを使うとどうなる?

上記dis_maxクエリで分かるように、各フィールド毎にbrown foxで検索されます。

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "brown fox",
      "type": "best_fields",
      "fields": ["title", "body"],
      "operator": "and"
    }
  },
  "size": 5,
  "from": 0
}
'

結果はbrown foxというフレーズが入ったdocument2のみ返ってきます。

{
  "took" : 179,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "Keeping pets healthy",
          "body" : "My quick brown fox eats rabbits on a regular basis."
        }
      }
    ]
  }
}

most_fields

クエリ

most_fieldsboolクエリとしてラップされるので

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "brown fox",
      "type": "most_fields",
      "fields": ["title", "body"]
    }
  },
  "size": 5,
  "from": 0
}
'

というクエリは

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "brown fox" } },
                { "match": { "body": "brown fox" } }
            ]
        }
    },
    "size": 5,
    "from": 0
}
'

として実行されます。

結果

document1document2の両方が返りました。
今回は偶然スコアが同じになりましたね。

{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "Keeping pets healthy",
          "body" : "My quick brown fox eats rabbits on a regular basis."
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "Quick brown rabbits",
          "body" : "Brown rabbits are commonly seen."
        }
      }
    ]
  }
}

operatorにandを使うとどうなる?

上記boolクエリで分かるように、各フィールド毎にbrown foxで検索されます。

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "brown fox",
      "type": "most_fields",
      "fields": ["title", "body"],
      "operator": "and"
    }
  },
  "size": 5,
  "from": 0
}
'

best_fieldsと同様に、brown foxというフレーズが入ったdocument2のみ返ってきます。

{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "Keeping pets healthy",
          "body" : "My quick brown fox eats rabbits on a regular basis."
        }
      }
    ]
  }
}

cross_fields

best_fieldsmost_fieldsは有用な一方で、operator: andの時に期待しないケースがあります。
例えばfirst_namelast_nameというフィールドがあり、検索クエリにfirst_name last_nameとした時にbest_fieldsmost_fieldsでは検索結果に出てこなくなります。
best_fieldsmost_fields各フィールドに対してAND検索を行うためです。

will smithと検索した時に

+(first_name:will  last_name:will)
+(first_name:smith last_name:smith)

のようにフィールドをまたいでAND検索をしたい時にこのcross_fieldsを使います。

クエリ

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "will smith",
      "type": "cross_fields",
      "fields": ["first_name", "last_name"]
    }
  },
  "size": 5,
  "from": 0
}
'

結果

document4のスコアが高いです。
default_operatorはORのため、smithの影響でdocument3も一応返っています。

{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "3",
        "_score" : 0.5753642,
        "_source" : {
          "first_name" : "Will",
          "last_name" : "Smith"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "4",
        "_score" : 0.2876821,
        "_source" : {
          "first_name" : "Smith",
          "last_name" : "Jones"
        }
      }
    ]
  }
}

クエリのexplain

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_validate/query?explain-d '
{
  "query": {
    "multi_match": {
      "query": "will smith",
      "type": "cross_fields",
      "fields": ["first_name", "last_name"]
    }
  }
}
'

結果

+(blended(terms:[first_name:will, last_name:will]) blended(terms:[first_name:smith, last_name:smith]))

これを見て分かるように、first_namelast_nameが結合されて1つのフィールドとして扱われ、それに対して各ワードが検索されています。

operatorにandを使うとどうなる?

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_search?pretty -d '
{
  "query": {
    "multi_match": {
      "query": "will smith",
      "type": "cross_fields",
      "fields": ["first_name", "last_name"],
      "operator": "and"
    }
  },
  "size": 5,
  "from": 0
}
'

document4だけが返るようになりました。

{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "3",
        "_score" : 0.5753642,
        "_source" : {
          "first_name" : "Will",
          "last_name" : "Smith"
        }
      }
    ]
  }
}

クエリのexplain

curl -s -H "Content-Type: application/json" \
  localhost:9200/my_index/my_type/_validate/query?explain -d '
{
  "query": {
    "multi_match": {
      "query": "will smith",
      "type": "cross_fields",
      "fields": ["first_name", "last_name"],
      "operator": "and"
    }
  }
}
'

結果

+(+blended(terms:[first_name:will, last_name:will]) +blended(terms:[first_name:smith, last_name:smith]))

先ほどと同様にfirst_namelast_nameが結合されて1つのフィールドとして扱われ、その上でAND検索されています。

  • document3Will Smithという1つのフィールドに
  • docuemnt4Smith Jonesという1つのフィールドに

なるため、will smithでAND検索した場合document3が返ることになります。

まとめ

Multi Match Queryは区別がしにくいため、きちんと用途を把握した上で使用すると良いです。

ソース