33
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elasticsearch で update 時にドキュメントの配列部分に"追加"したい

Last updated at Posted at 2014-05-23

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)'
解決策のupdate文
{
  "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)'
update文
{
  "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)'
update文×2
{
  "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)'
update文
{
  "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
          }
       ]
    }
  }
}
33
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?