从 Elasticsearch 文档中删除一个字段。

100

我需要在所有索引到 Elasticsearch 的文档中删除一个字段。该如何操作?


5
默认情况下是不可能的,因为目前Lucene不支持这样做。基本上你只能将整个Lucene文档放入或从Lucene索引中删除。1 获取您的文档的第一个版本 2 删除该字段 3 推送该文档的新版本。 - backtrack
感谢分享https://dev59.com/dF4b5IYBdhLWcg3whSFC#53771354 Thiagofalcao。这很棒! - Mark
我能完成这个任务的唯一方法是通过重新映射带有删除字段值的索引。请查看https://dev59.com/qmQo5IYBdhLWcg3wUN7Z#38874129。 - j7skov
8个回答

126

@backtrack所说的是正确的,但是在Elasticsearch中有一种非常方便的方法来完成这个操作。 Elasticsearch将抽象出删除的内部复杂性。 您需要使用Update API来实现此目的 -

curl -XPOST 'localhost:9200/test/type1/1/_update' -d '{
    "script" : "ctx._source.remove(\"name_of_field\")"
}'

您可以在此处找到更多文档。

注意:自Elastic Search 6版本起,您需要包含一个content-type头。

-H 'Content-Type: application/json'

4
如果你有10亿篇具有该字段的文档,那么它的性能如何? - Henley
1
每次进行此类更改时,实际文档将被删除并添加新文档。 - Vineeth Mohan
1
针对 ElasticSearch 5.0 的提示:您应该使用命名参数而不是硬编码的名称。参数更快,并且不会破坏脚本编译限制。请参考文档 - Antony Zh
2
@VineethMohan它也会删除映射吗?如果我查询test/type1/_mapping,它会显示“name_of_field”吗? 所以基本上,我想删除并添加具有不同类型的相同字段名称。这可能吗? - lrathod
1
另外,如果您正在使用连字符引用嵌套字段,则可以使用 ctx._source[\"my-field\"].remove(\"subfield\") - Martin Foot

53

Elasticsearch在2.3版本中添加了update_by_query。这个实验接口允许你针对匹配查询的所有文档进行更新。

内部elasticsearch执行扫描/滚动以收集文档批次,然后像批量更新界面一样更新它们。这比使用自己的扫描/滚动界面手动执行更快,因为没有网络和序列化开销。每条记录必须加载到RAM中,修改后写入。

昨天我从我的ES集群中删除了一个大字段。在update_by_query期间,我看到持续的每秒10,000条记录吞吐量,受CPU限制而不是IO。

如果集群有其他更新流量,或者当其中一个记录在批处理之一下更新时,会出现ConflictError,则需要设置conflicts=proceed。否则整个作业将停止。

同样,设置wait_for_completion=false将导致update_by_query通过tasks接口运行。否则,如果连接关闭,作业将终止。

url:

http://localhost:9200/INDEX/TYPE/_update_by_query?wait_for_completion=false&conflicts=proceed

请求正文:

{
  "script": "ctx._source.remove('name_of_field')",
  "query": {
    "bool": {
      "must": [
        {
          "exists": {
            "field": "name_of_field"
          }
        }
      ]
    }
  }
}

从 Elasticsearch 1.43 开始,默认情况下禁用内联的 groovy 脚本。您需要在配置文件中添加 script.inline: true 来启用内联脚本。

或者将 groovy 上传为脚本并使用 "script": { "file": "scriptname", "lang": "groovy"} 格式。


我还不知道如何回收该字段使用的field_data空间。希望滚动重启会导致序数重新加载。 - spazm
3
身体需要轻微修改,但除此之外这个工作完美无缺。我不得不将脚本包装在一个 JSON 对象中,可能是因为 API 有些变化。 - Peter
1
如果我的字段是数组的一部分(例如 [{"name":"test"},{"name":"test1"}] ),并且我不知道该字段在数组中的索引,那么我如何使用上述查询删除该字段呢?请帮帮我。 - Suraj Dalvi
1
@sayed-abolfazl-fatemi,感谢您对代码进行的清理编辑。 - spazm
1
这太棒了。谢谢! - user1217

47

您可以使用 _update_by_query

示例 1

索引:my_index

字段:user.email

POST my_index/_update_by_query?conflicts=proceed
{
    "script" : "ctx._source.user.remove('email')",
    "query" : {
        "exists": { "field": "user.email" }
    }
}

例子2

索引:my_index

字段:total_items

POST my_index/_update_by_query?conflicts=proceed
{
    "script" : "ctx._source.remove('total_items')",
    "query" : {
        "exists": { "field": "total_items" }
    }
}

嗨,我尝试了你在这里提到的完全相同的方法,但它似乎对我没有用。我是这个领域的新手。我在我的索引中有一个不需要的字段,比如索引名称为“test_xyz”。它包含几乎155154个文档。我想从我的索引中删除一个不需要的字段。这就是我的索引模式在JSON格式下的样子{A : {B : {C: } } }。我基本上想删除B - 这意味着C将自动从我的索引中删除。为了做到这一点,我使用了你的第一个想法。把A作为用户,B作为电子邮件。你能帮我解决一下吗? - Maunil Vyas
嗨@MaunilVyas,它应该可以工作:POST my_index/_update_by_query?conflicts=proceed { "script" : "ctx._source.A.remove('B')", "query" : { "exists": { "field": "A.B" } } }如果它不能工作,您需要使用正确的映射将目标索引重新索引数据。 - Thiago Falcao
1
谢谢您的回复。是的,现在它可以工作了。我只是犯了一些小错误! - Maunil Vyas
1
你好,这个操作同时也会从映射中删除该字段吗?不会吧? - srcnaks
1
这个答案帮助我理解了如何删除嵌套字段。谢谢! - Akhilesh Bhatia
显示剩余3条评论

14

之前的答案对我没有用。

我不得不添加关键字"inline":

POST /my_index/_update_by_query
{
  "script": {
    "inline": "ctx._source.remove(\"myfield\")"
  },
  "query" : {
      "exists": { "field": "myfield" }
  }
}

9

默认情况下,这是不可能的,因为现在的Lucene不支持这样做。基本上,你只能将整个Lucene文档放入或从Lucene索引中删除。

  1. 获取文档的第一版本
  2. 删除该字段
  3. 推送该文档的新版本

这个答案适用于ES 5版本以下。


2
@ThomasDecaux,非常感谢。我在2015年已经回答过了,我知道ES现在有这个能力。再次感谢您的指出。 - backtrack
@ThomasDecaux - 的确如此。感谢您宝贵的反馈。我刚刚添加了版本。 - backtrack

3
PUT /products/_update/1
{
  "docs" :{
    "price": 12,
    "quantity": 3,
    "in_stock": 6
  }
}

Now if I need to remove "quantity" then:

POST products/_update/1
{
  "script": {
    "source": "ctx._source.remove(\"quantity\")"
  }
}

3
我想补充之前的回答,即删除字段后,索引的大小不会改变。您需要创建一个新的索引或使用_reindex api。
curl -X POST "localhost:9200/_reindex?pretty" -H 'Content-Type: application/json' -d'
{
 "source": {
   "index": "old-index"
 },
 "dest": {
   "index": "new-index"
}}

'

1
这是不正确的,删除一定数量的文档段后,它们会合并并释放磁盘空间。唯一保留的是模式中的字段。 - Arman Ordookhani

3
对于坚持使用批量API的用户,删除文档中某些字段的替代方法是在批量API调用的update操作有效载荷中提供额外的脚本。
命令部分与官方文档描述相同:
curl -s -H "Content-Type: application/x-ndjson"  -H "Accept: application/json; indent=4;" \
     --data-binary   '@es_bulk_edit_data.json'  --request POST \
     "http://YOUR_ELASTICSEARCH_HOST:PORT_NUM/OPTIONAL_INDEX/OPTIONAL_TYPE/_bulk?pretty"

在请求体文件中,您可能需要使用两个有效载荷来处理同一文档,其中一个用于创建、更新字段,另一个用于通过脚本删除字段,如下所示:
// assume you attempt to add one field `artist`, update one field `num_views`,
// and delete one field `useless` in the document with type t1 and ID 123
{"update": {"_type": "t1", "_id": "123"}}
{"doc": {"artist": "new_artist", "num_views": 67}}
{"update": {"_type": "t1", "_id": "123"}}
{"script": {"source": "ctx._source.remove(params.del_field_name)", "lang":"painless", "params":{"del_field_name": "useless"}}}

注意:

  • 在批量API中,doc部分不能与script部分放置在同一个有效载荷中,ElasticSearch似乎拒绝处理这种有效载荷结构并返回错误响应400 bad request,原因消息将是Validation Failed: 1: can't provide both script and doc;。这就是为什么我将删除和所有其他操作分成2个有效载荷的原因。
  • 这些已在版本5.6和6.6上进行了测试,最新版本(v7.10)也应该得到相同的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接