Elasticsearchで、ドキュメントのフィールドが配列(連想配列)の場合に、そのフィールドの配列に値を追加したい。
( Elasticsearchネタはほぼ自分用メモですw )
参考にしたのは Elasticsearch公式サイト。
単純な配列の場合
例としてインデックス名「 sample 」、タイプ名「 mytype 」で作成。
$ curl -XPUT 'http://localhost:9200/sample' -d '(下のJSON)'
{
"mappings": {
"mytype": {
"_source": {
"enabled": true
},
"properties": {
"name": {
"type": "string"
},
"tags": {
"type": "string" <「string」で良い
}
}
}
}
}
まずは普通にデータを入れる
$ curl -XPUT 'http://localhost:9200/sample/mytype/1' -d '(下のJSON)'
{
"name": "データ1",
"tags": ["hoge"] <ここに値を追加したい
}
updateしてみる (失敗例)
$ curl -XPOST 'http://localhost:9200/sample/mytype/1/_update' -d '{"tags": ["fuga"]}'
上記でupdate後に検索してみると、結果は以下のように、「tags」に指定した値が単純に置き換わってしまう。
{
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "sample",
"_type": "mytype",
"_id": "1",
"_score": 1,
"_source": {
"name": "データ1",
"tags": ["fuga"] <「hoge」は残らない
}
}
]
}
}
解決策
この場合は「 script
」という機能を使って、JavaScriptで配列に追加することができる。
さらに、「 upsert
」という機能で、未登録のドキュメントなら新規登録扱いにできる。
(MySQLでいう ON DUPLICATE KEY UPDATE )
$ curl -XPOST 'http://localhost:9200/sample/mytype/1/_update' -d '(下のJSON)'
{
"params" : {
"val" : "fuga"
},
"script" : "ctx._source.tags += val",
"upsert": {
"name": "データ1",
"tags": ["fuga"]
}
}
params
で設定したキーのもの(上記では val
)が、 script
内で使えるようになっている。
(JavaScriptだけど push()
ではなく +=
な点に注意)
ドキュメントがない場合は upsert
に設定したものが挿入される。
なので、最初にデータを入れておく必要はない。
その結果、以下のようになる。
{
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "sample",
"_type": "mytype",
"_id": "1",
"_score": 1,
"_source": {
"name": "データ1",
"tags": [
"hoge",
"fuga" <追加できた
]
}
}
]
}
}
重複しないようにしたい
前述のように upsert
を繰り返していると、配列に同じ値がどんどん入ってしまいがち。
その場合、連想配列にする。(他の方法がわからなかった)
フィールドに連想配列を入れるには、マッピングで object
に設定する必要がある。
$ curl -XPUT 'http://localhost:9200/sample' -d '(下のJSON)'
{
"mappings": {
"mytype": {
"_source": {
"enabled": true
},
"properties": {
"name": {
"type": "string"
},
"tags": {
"type": "object" <ここが最初と違う
}
}
}
}
}
そして upsert する
$ curl -XPOST 'http://localhost:9200/sample/mytype/1/_update' -d '(下のJSON)'
{
"params" : {
"val" : "hoge"
},
"script" : "ctx._source.tags[val] = val",
"upsert": {
"name": "データ1",
"tags": {
"hoge": "hoge"
}
}
}
ctx._source.tags[val] = val
の部分で同じ値は上書きするようにしてる。
さらに upsert (「tags」に「fuga」を追加) を2回やってみる
$ curl -XPOST 'http://localhost:9200/sample/mytype/1/_update' -d '(下のJSON)'
{
"params" : {
"val" : "fuga"
},
"script" : "ctx._source.tags[val] = val",
"upsert": {
"name": "データ1",
"tags": {
"fuga": "fuga"
}
}
}
//もっかい同じデータ
{
"params" : {
"val" : "fuga"
},
"script" : "ctx._source.tags[val] = val",
"upsert": {
"name": "データ1",
"tags": {
"fuga": "fuga"
}
}
}
その結果、以下のようになる。
{
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "sample",
"_type": "mytype",
"_id": "1",
"_score": 1,
"_source": {
"name": "データ1",
"tags": {
"hoge": "hoge",
"fuga": "fuga" <重複せず追加できた
}
}
}
]
}
}
ちなみに 特定の配列だけ削除 する方法は不明・・・。
update時に連想配列の値を null にしたり、JavaScriptの delete
使ったりしてみたけど無理だった。
データ取得 -> 値を削除 -> 上書きupdateするしかなさそう。
で、これ何に使うのかというと、レコメンデーション(「◯◯を見てる人はこれも見てます」的なやつ)を作る時に実験的にやってみたもの。
例えば商品Aを見た後に、そのユーザーが商品Bを見たら、その履歴を同じドキュメントに追記していきたかった感じ。
そうすると後でファセット検索して閲覧数でソートできそうだったので。
(まだ実際には作ってはいないw)
- - -
追記:配列が重複登録されない方法あった
インデックス作成
$ curl -XPUT 'http://localhost:9200/sample' -d '(下のJSON)'
{
"mappings": {
"mytype": {
"_source": {
"enabled": true
},
"properties": {
"name": {
"type": "string"
},
"tags": {
"type": "string" <「string」で良い
}
}
}
}
}
update (insert)
$ curl -XPOST 'http://localhost:9200/sample/mytype/1/_update' -d '(下のJSON)'
{
"params" : {
"val" : "hoge"
},
"script" : "if(ctx._source.tags.indexOf(val)==-1){ctx._source.tags+=val}",
"upsert": {
"name": "データ1",
"tags": {
"hoge": ["hoge"]
}
}
}
JavaScript部分で indexOf
使って配列を検索できるっぽいので、見つかれば入れない、なければ入れる。
連想配列にしなくてもこれでいけた。
以下はファセット検索の例
$ curl -XGET 'http://localhost:9200/sample/mytype/_search?search_type=count' -d '(下のJSON)'
{
"facets": {
"ids": {
"terms": {
"field" : "tags",
"size": 10,
"order" : "count"
}
}
}
}
{
"facets": {
"ids": {
"_type": "terms",
"missing": 0,
"total": 3,
"other": 0,
"terms": [
{
"term": "hoge",
"count": 2
},
{
"term": "fuga",
"count": 1
}
]
}
}
}