Elasticsearch で前方一致検索を行う際に、検索文字列に正規化を行いたい時があります。
その場合、検索を実行するアプリケーション側で正規化を行うこともできますが、Elasticsearch側で正規化を行うことも可能です。
手元の環境は以下になります。
- Ubuntu 18.04 LTS
- Elasticsearch 7.2.1
- ICU Analysis Plugin
最初にインデックスの設定とマッピングを定義します。( jq コマンドで整形してます。)
Wildcard query は Term-level queries になるため、アナライザが適用されません。
そのため、Match query のようにアナライザの設定でフィルターを指定するのではなく、normalizer を直接指定する必要があります。
今回は goods データに name フィールドのみの状態を作ってみます。
$ curl -X PUT 'localhost:9200/shop' -H 'Content-Type: application/json' -d'
{
"settings": {
"index": {
"number_of_shards": 2,
"number_of_replicas": 1,
"refresh_interval": "-1",
"analysis": {
"normalizer": {
"shop_normalizer": {
"type": "custom",
"char_filter": [
"icu_normalizer"
]
}
}
}
}
},
"mappings": {
"properties": {
"type": {"type": "keyword"},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"normalizer": "shop_normalizer",
"ignore_above": 256
}
}
}
}
}
}' | jq
次に、以下のようなデータで json ファイルを作成します。
カタカナ、および半角スペースが混じったデータにしてみました。
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.1"}}
{"type": "goods","name": "タンカンジャム"}
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.2"}}
{"type": "goods","name": "ジェラード 屋久島"}
そして、json ファイルを使ってインデクシングします。
$ curl -H "Content-type: application/x-ndjson" -X POST http://localhost:9200/_bulk?refresh=false --data-binary @request_bulk.json | jq
$ curl -X POST 'localhost:9200/shop/_refresh' | jq
全角と半角のどちらの文字列でも検索できることを確認します。
$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
"from": 0,
"size": 10,
"query": {
"wildcard": {
"name.keyword": {
"value": "タンカン*"
}
}
}
}
' | jq
以下のように結果が返ってきます。
{
"took": 9,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "shop",
"_type": "_doc",
"_id": "goods_id.1",
"_score": 1,
"_source": {
"type": "goods",
"name": "タンカンジャム"
}
}
]
}
}
$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
"from": 0,
"size": 10,
"query": {
"wildcard": {
"name.keyword": {
"value": "タンカン*"
}
}
}
}
' | jq
以下のように結果が返ってきます。
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "shop",
"_type": "_doc",
"_id": "goods_id.1",
"_score": 1,
"_source": {
"type": "goods",
"name": "タンカンジャム"
}
}
]
}
}
全角スペースが混じった文字列でも検索できることを確認します。
$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
"from": 0,
"size": 10,
"query": {
"wildcard": {
"name.keyword": {
"value": "ジェラード 屋久*"
}
}
}
}
' | jq
以下のように結果が返ってきます。
{
"took": 8,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "shop",
"_type": "_doc",
"_id": "goods_id.2",
"_score": 1,
"_source": {
"type": "goods",
"name": "ジェラード 屋久島"
}
}
]
}
}
Wildcard query を実行する際に rewrite パラメータで挙動を制御できるようですが、基本的にはデフォルトの値の方がパフォーマンスが良いようです。