Elasticsearch 先輩で価格周りを触りたい

やりたいこと

1000 JPY みたいな文字列をいい感じに数値検索の対象にできないかなという実験。

今回の実験は以下のデータに対して、実験方法の curl を実行し、正しく指定した範囲のデータが返ってくることができるか?ということを調べる。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"PRICE1":"3000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"PRICE1":"5000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"3"}}
{"PRICE1":"100 JPY"}
$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 10,
                "lte": 3000
             }
        }
    }
}'

# id 1, 2 のみ検索結果にヒットすることを期待している。

自動マッピングに任せる

$ curl -X POST -H 'Content-Type: application/x-ndjson' 'http://localhost:9200/_bulk?pretty&refresh' --data-binary "@price.json"
$ curl -XGET 'localhost:9200/sample_index?pretty'
{
  "sample_index" : {
    "aliases" : { },
    "mappings" : {
      "sample_type" : {
        "properties" : {
          "PRICE1" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "1500889167417",
        "number_of_shards" : "5",
        "number_of_replicas" : "1",
        "uuid" : "eZdsR51MRkeqfem48th0Rw",
        "version" : {
          "created" : "6000002"
        },
        "provided_name" : "sample_index"
      }
    }
  }
}

text 型で入ってるため、上手く数値の範囲検索を行うことが出来ない。

Numeric Detection

Dynamic field mapping | Elasticsearch Reference [5.5] | Elastic

text 型で入ってきた数値に関して、検索できるようにするぜという設定。 これによって、 以下のような text の中身が数値というデータに関しては、数値検索可能となる。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"PRICE1":"1000"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"PRICE1":"10"}
$ curl -XPUT 'localhost:9200/sample_index?pretty' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "sample_type": {
      "numeric_detection": true
    }
  }
}'
{
  "acknowledged" : true,
  "shards_acknowledged" : true
}
$ curl -X POST -H 'Content-Type: application/x-ndjson' 'http://localhost:9200/_bulk?pretty&refresh' --data-binary "@test-price.json"
$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 100,
                "lte": 1000
             }
        }
    }
}'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "PRICE1" : "1000"
        }
      }
    ]
  }
}

Tokenize

あとは、 100 JPY となっている部分を 100JPY に分けたら 100 に対する検索にヒットするようになりそうなので、やってみる。

$ curl -XPOST 'localhost:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
  "tokenizer": "whitespace",
  "text": "100 JPY"
}
'

{
  "tokens" : [
    {
      "token" : "100",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "JPY",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "word",
      "position" : 1
    }
  ]
}

空白で、トークンを区切る whitespace が使えそうなので、これでトークンを 100JPY に分けることはできそう。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"analyzer": "whitespace","PRICE1":"3000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"analyzer": "whitespace","PRICE1":"5000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"3"}}
{"analyzer": "whitespace","PRICE1":"100 JPY"}

というわけで、実験データに analyzer を足した物を実験データとして再度やり直した。

しかし、挙動が安定しない 意図通りの挙動になってくれない感じバグっぽい気もしている。 またフォーラム行きかしら。。。

ちなみにこんな感じ↓ 100000 以上について指定している。

$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 100000
             }
        }
    }
}'

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "5000 JPY"
        }
      },
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "3000 JPY"
        }
      },
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "100 JPY"
        }
      }
    ]
  }
}

そもそもトークンが上手くいっていない感じだな。。。うーん。。。わからん。。。。

$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
         "term": { "PRICE1": "JPY"}
        }
    }
}'

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

【追記】

johtani さんからアドバイスを頂いた。

アプローチの仕方が間違っていた。 やっぱり、数値にアプリ側で変換してインデックス貼ってあげるのが楽な気がする。