Carpe Diem

備忘録。https://github.com/jun06t

Prefix Query の注意

概要

ElasticsearchのPrefix Query を使用する時に詰まったところをまとめました。

環境

  • Elasticsearch 2.3.2

通常の利用方法

マッピング・データ用意

マッピングを設定します。

項目 設定値
インデックス名 test
タイプ名 internet
フィールド名 name

で設定します。

$ curl -XPUT 'localhost:9200/test' -d '
{
    "mappings": {
        "internet": {
            "_all": {
                "enabled": false
            },
            "dynamic": false,
            "properties": {
                "name": {
                    "type": "string",
                    "index": "not_analyzed"
                }
            }
        }
    }
}
'

データを用意します。

$ curl -s -XPOST localhost:9200/test/_bulk -d '
{"index": {"_type": "internet", "_id": "1"}}
{"name": "fate"}
{"index": {"_type": "internet", "_id": "2"}}
{"name": "refactor"}
{"index": {"_type": "internet", "_id": "3"}}
{"name": "facebook"}
'

検索クエリ

faで検索します。

$ curl localhost:9200/test/internet/_search?pretty -d '
{
    "query": {
        "prefix": {
            "name": {
                "value": "fa"
            }
        }
    },
    "size": 5,
    "from": 0
}
'

結果

  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "test",
      "_type" : "internet",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "name" : "fate"
      }
    }, {
      "_index" : "test",
      "_type" : "internet",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "name" : "facebook"
      }
    } ]
  }

fatefacebookがヒットしました。スコアは両方共1.0です。感覚的に問題無いですね。

Normalize、ひらがな、カタカナの扱い

通常の検索データを用意する際は、表記ゆれを吸収するためにNormalizeであったり、ひらがな→カタカナの変換を行います。
しかしながらPrefix Queryはnot_analyzedで検索クエリを投げるので以下のケースでヒットしなくなります。

事前準備

あらかじめicu-analyzerをインストールしておきます。

# ./bin/plugin install analysis-icu 

マッピング・データ用意

使用するtokenizerはkeyword analyzerです。これはnot_analyzedと同じように使えるので、「analyzeする必要はないけど、Normalizeとかはさせたい」といった時に使えます。

項目 設定値
インデックス名 katakana
タイプ名 internet
フィールド名 name

で設定します。

$ curl -XPUT 'localhost:9200/katakana' -d '
{
    "settings": {
        "analysis": {
            "analyzer": {
                "kana_analyzer": {
                    "tokenizer": "keyword",
                    "filter": [
                        "icu_normalizer",
                        "asciifolding",
                        "kana_filter"
                    ]
                }
            },
            "filter": {
                "kana_filter": {
                    "type": "icu_transform",
                    "id": "Hiragana-Katakana"
                }
            }
        }
    },
    "mappings": {
        "internet": {
            "_all": {
                "enabled": false
            },
            "dynamic": false,
            "properties": {
                "name": {
                    "type": "string",
                    "analyzer": "kana_analyzer"
                }
            }
        }
    }
}
'

データを登録します。

$ curl -s -XPOST localhost:9200/katakana/_bulk -d '
{"index": {"_type": "internet", "_id": "1"}}
{"name": "あいうえお"}
{"index": {"_type": "internet", "_id": "2"}}
{"name": "かきくけこ"}
{"index": {"_type": "internet", "_id": "3"}}
{"name": "あいぱっど"}
'

検索クエリ

あいで検索します。感覚的にはあいうえおあいぱっどがヒットして欲しいです。

$ curl localhost:9200/katakana/internet/_search?pretty -d '
{
    "query": {
        "prefix": {
            "name": {
                "value": "あい"
            }
        }
    },
    "size": 5,
    "from": 0
}
'

結果

  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }

ヒットしませんでした。検索クエリのあいは、ひらがなのまま渡されるので、インデックスにカタカナ変換されたデータとマッチしません。


検索クエリ(カタカナ)

逆に言うとクエリに投げる時にカタカナに変換しておけばヒットします。

$ curl localhost:9200/katakana/internet/_search?pretty -d '
{
    "query": {
        "prefix": {
            "name": {
                "value": "アイ"
            }
        }
    },
    "size": 5,
    "from": 0
}
'

結果

  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "katakana",
      "_type" : "internet",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "name" : "あいうえお"
      }
    }, {
      "_index" : "katakana",
      "_type" : "internet",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "name" : "あいぱっど"
      }
    } ]
  }

ちゃんとヒットしました。

N-gramにアナライズしたフィールド

N-gramなどでアナライズした場合はどうなるのでしょうか?
感覚的にはヒットして欲しいですが、スコアがどうなるかなどが直感的にわかりにくそうです。

マッピング・データ用意

項目 設定値
インデックス名 ngram
タイプ名 internet
フィールド名 name

で設定します。

$ curl -XPUT 'localhost:9200/ngram' -d '
{
    "settings": {
        "analysis": {
            "analyzer": {
                "ngram_analyzer": {
                    "tokenizer": "ngram_tokenizer"
                }
            },
            "tokenizer": {
                "ngram_tokenizer": {
                    "type": "nGram",
                    "min_gram": "2",
                    "max_gram": "3",
                    "token_chars": [
                        "letter",
                        "digit"
                    ]
                }
            }
        }
    },
    "mappings": {
        "internet": {
            "_all": {
                "enabled": false
            },
            "dynamic": false,
            "properties": {
                "name": {
                    "type": "string",
                    "analyzer": "ngram_analyzer"
                }
            }
        }
    }
}
'

データを登録します。

$ curl -s -XPOST localhost:9200/ngram/_bulk -d '
{"index": {"_type": "internet", "_id": "1"}}
{"name": "あいうえお"}
{"index": {"_type": "internet", "_id": "2"}}
{"name": "かきくけこ"}
{"index": {"_type": "internet", "_id": "3"}}
{"name": "あいぱっど"}
'

検索クエリ

$ curl localhost:9200/ngram/internet/_search?pretty -d '
{
    "query": {
        "prefix": {
            "name": {
                "value": "あい"
            }
        }
    },
    "size": 5,
    "from": 0
}
'

結果

  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "ngram",
      "_type" : "internet",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "name" : "あいうえお"
      }
    }, {
      "_index" : "ngram",
      "_type" : "internet",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "name" : "あいぱっど"
      }
    } ]
  }

ちゃんとヒットしました。スコアはやはり1.0ですね。

ちなみにn-gramは2~3文字で設定したので、それ以上の文字数を投げると当然ヒットしません。


検索クエリ(長い)

$ curl localhost:9200/ngram/internet/_search?pretty -d '
{
    "query": {
        "prefix": {
            "name": {
                "value": "あいうえ"
            }
        }
    },
    "size": 5,
    "from": 0
}
'

結果

  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }

予想どおりヒットしないですね。

ソース