termクエリを使ってみる

そう言えば、elasticsearch勉強したことあるけれど、あまり検索にフォーカスしていなかったなっと思いました。mappingとか、indexとかその辺りの事中心だった気がします。(もう忘れたけれど)

term って和訳すると、「用語」「言葉」って言うらしいです。

term は完全一致で検索する時に使うみたいですね。 keyword タイプのフィールドに使うのが一般的なのかも? term クエリの説明ページではそんなこと書いてありませんが、 text タイプのフィールドに使うのはオススメしないって言ってますね。

https://www.elastic.co/guide/en/elasticsearch/reference/8.3/query-dsl-term-query.html

逆に keyword 以外だとどんなフィールドに使うんだろうか? Numbers boolean とかかな? 忘れました。elasticsearchのデータタイプ全部使ったことないなと。

https://www.elastic.co/guide/en/elasticsearch/reference/8.3/mapping-types.html

公式ドキュメントに term クエリを実行したケースと、 match クエリを実行したケースの違いが載っていたので実際に手元で確認してみます。

まず、 text タイプを一つ持つインデックスを作り、ドキュメントを作成しました。

 PUT my-index
 {
   "mappings": {
     "properties": {
       "full_text": {
         "type": "text"
       }
     }
   }
 }
 
 PUT my-index/_doc/1
 {
   "full_text": "Quick Brown Foxes!"
 }

以下のように、上記のドキュメントを term クエリで検索しても結果が返ってきません。 full_text フィールドは text タイプなので、インデックスされるときにAnalyzerによって分析されるので、全く同じテキストで検索しても出てこないってことですね。

以下、公式ドキュメントより。

Because the full_text field no longer contains the exact term Quick Brown Foxes!, the term query search returns no results.

textタイプに合った検索、matchクエリを使うとこのドキュメントを取得できます。

 GET my-index/_search
 {
   "query": {
     "match": {
       "full_text": "Quick Brown Foxes!"
     }
   }
 }
 
 // response
 {
   "took" : 4,
   "timed_out" : false,
   "_shards" : {
     "total" : 1,
     "successful" : 1,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : {
       "value" : 1,
       "relation" : "eq"
     },
     "max_score" : 0.8630463,
     "hits" : [
       {
         "_index" : "my-index",
         "_id" : "1",
         "_score" : 0.8630463,
         "_source" : {
           "full_text" : "Quick Brown Foxes!"
         }
       }
     ]
   }
 }

match クエリなので、一部分だけでもヒットすればドキュメントを取ることができます。(このあたりの詳しい話は忘れました。多分Analyzerで保存されているテキストも、検索するテキストも、トークン化(だっけ?)して突合していたはず!)

 GET my-index/_search
 {
   "query": {
     "match": {
       "full_text": "Quick"
     }
   }
 }

ちなみに、 term クエリでも、 match クエリでも、どちらでも検索できるようにしておきたい!というユースケースに対応できるようにelasticsearchでは、multi fieldという機能があります。こいつを使えば、どちらのクエリでもひっかけるようにできますね。

fields | Elasticsearch Guide [8.3] | Elastic

multi fieldは、一つのフィールドに対し複数のタイプを関連付けることができる機能です。なので、 textkeyword を同時に割り当てることができます(便利ですね)。おんなじ名前にはできないので、それぞれ別別のフィールド名を割り当てることになりますね。

例えば次のようにマッピングを定義しておけば、上記のドキュメントはどちらでもヒットするようにできます。

PUT my-index
{
  "mappings": {
    "properties": {
      "full_text": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

PUT my-index/_doc/1
{
  "full_text": "Quick Brown Foxes!"
}

GET my-index/_search
{
  "query": {
    "match": {
      "full_text": "Quick" // ←ヒットする!
    }
  }
}

GET my-index/_search
{
  "query": {
    "term": {
      "full_text.keyword": "Quick Brown Foxes!" // ←ヒットする!
    }
  }
}