Elasticsearch におけるドキュメントの更新は、ドキュメントの登録の場合と同じく、いくつかやり方がありそうです。 こちらも自分なりに調べた結果をまとめてみます。
Index API の _doc エンドポイント
前回の記事で調べた通り、_doc
エンドポイントでは既存のドキュメントに対して、同じドキュメント ID で PUT
または POST
メソッドでリクエストを行うことで、ドキュメントの更新が可能です。
ここでは、あらかじめ以下のようにドキュメントを登録しておいたものとします。
PUT test-index/_doc/1 { "field1": "foo" }
この時、以下のようにリクエストを行うことで、フィールドの値を変更することができます。
PUT test-index/_doc/1 { "field1": "bar" } # レスポンス { "_index": "test-index", "_id": "1", "_version": 2, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
レスポンスの result
フィールドが updated
となっていて、更新が行われたことがわかります。POST
メソッドを用いた場合も、同様の応答となります。
更新されたドキュメントを実際に取得してみると、フィールドの値が更新時に指定したものになっています。
GET test-index/_doc/1 # レスポンス { "_index": "test-index", "_id": "1", "_version": 2, "_seq_no": 1, "_primary_term": 1, "found": true, "_source": { "field1": "bar" } }
では、異なるフィールド名を指定した場合はどうなるか試してみます。
PUT test-index/_doc/1 { "field2": "bar" }
この場合も同様にレスポンスの result
フィールドは updated
となりますが、更新されたドキュメントを確認してみると、以下のようになっています。
GET test-index/_doc/1 # レスポンス { "_index": "test-index", "_id": "1", "_version": 3, "_seq_no": 2, "_primary_term": 1, "found": true, "_source": { "field2": "bar" } }
更新後のドキュメントには field2
が含まれていますが、field1
は含まれていません。つまりここでのドキュメントの更新は、全体の上書き、または置き換えになっています。
更新時に既存のドキュメントのすべてのフィールドとその値を記述し、加えて追加のフィールドと値を記述することで、フィールドを追加することができます。しかし、既存のドキュメントが多数のフィールドを含む場合にはこれは大変な作業になります。
では、既存ドキュメントの一部のみを更新したい場合には、どうするのが良いのでしょうか。
Update API を使ったドキュメントの更新
既存のドキュメントの一部を更新する方法について、公式ドキュメント では以下のように紹介されています。
Update part of a documentedit
The following partial update adds a new field to the existing document:
POST test/_update/1 { "doc": { "name": "new_name" } }
前節の例に続けて以下のようにすることで、フィールドの追加を行うことができます。
POST test-index/_update/1 { "doc": { "field3": "baz" } } # レスポンス { "_index": "test-index", "_id": "1", "_version": 4, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 3, "_primary_term": 1 }
更新されたドキュメントを見てみると、field3
が追加され、もともとあった field2
も残っていることがわかります。
GET test-index/_doc/1 { "_index": "test-index", "_id": "1", "_version": 4, "_seq_no": 3, "_primary_term": 1, "found": true, "_source": { "field2": "bar", "field3": "baz" } }
もちろん、既存のフィールドの値を変更することもできます。
POST test-index/_update/1 { "doc": { "field3": "foo" } } GET test-index/_doc/1 { "_index": "test-index", "_id": "1", "_version": 5, "_seq_no": 4, "_primary_term": 1, "found": true, "_source": { "field2": "bar", "field3": "foo" } }
ここでは、更新時に指定した field3
の値が変更されている一方で、指定していない field2
については元の値のままになっています。
既存ドキュメントのフィールドを削除する方法
既存のドキュメントに対して、フィールドの値の変更やフィールドの追加を行えることがわかりました。
_doc
エンドポイントを用いる方法では、更新時に指定した内容でドキュメントの上書きを行うため、必要ないフィールドについては更新時に指定しないことで、結果として削除することができます。
しかし、やはり多数のフィールドを持つドキュメントから少数のフィールドを削除したい場合には、記述する内容が多くなるため、大変な作業になります。
Update API を用いて、指定したフィールドのみを削除する方法はないものでしょうか。
まず思いつくのは、値を null
にすることです。
PUT test-index/_doc/1 { "field2": null } # レスポンス { "_index": "test-index", "_id": "1", "_version": 6, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 5, "_primary_term": 1 }
しかしこの方法では、対象のフィールドの値が null
に変更されるのみで、フィールドそのものは削除されません。
GET test-index/_doc/1 # レスポンス { "_index": "test-index", "_id": "1", "_version": 6, "_seq_no": 5, "_primary_term": 1, "found": true, "_source": { "field2": null, "field3": "foo" } }
Update APIでは、doc
要素を指定して更新を行うだけでなく、スクリプト実行を実行することができ、これによりフィールドの削除を行うことができます。
(むしろ公式ドキュメントではスクリプトの実行の方が先に記載されています)
Conversely, this script removes the field new_field:
POST test/_update/1 { "script" : "ctx._source.remove('new_field')" }
前節までの例では、以下のようにして field2
を削除することができます。
POST test-index/_update/1 { "script": "ctx._source.remove('field2')" } { "_index": "test-index", "_id": "1", "_version": 6, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 5, "_primary_term": 1 } GET test-index/_doc/1 { "_index": "test-index", "_id": "1", "_version": 6, "_seq_no": 5, "_primary_term": 1, "found": true, "_source": { "field3": "foo" } }