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
となっている部分を 100
と JPY
に分けたら 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
が使えそうなので、これでトークンを 100
と JPY
に分けることはできそう。
{"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 さんからアドバイスを頂いた。
numeric_detectionは文字が数値に型変換できるなら数値にするという機能であり、トークナイズとは関係ないので望んでる動きにはならないかと。
— Jun Ohtani (@johtani) 2017年7月25日
アプローチの仕方が間違っていた。 やっぱり、数値にアプリ側で変換してインデックス貼ってあげるのが楽な気がする。