更新 API编辑

使用指定的脚本更新文档。

请求编辑

POST /<index>/_update/<_id>

先决条件编辑

  • 如果启用了 Elasticsearch 安全功能,您必须对目标索引或索引别名具有 indexwrite 索引权限

描述编辑

使您能够对文档进行脚本更新。脚本可以更新、删除或跳过修改文档。更新 API 还支持传递部分文档,该文档将合并到现有文档中。要完全替换现有文档,请使用 index API

此操作

  1. 从索引中获取文档(与分片并置)。
  2. 运行指定的脚本。
  3. 索引结果。

文档仍然需要重新索引,但使用 update 可以减少一些网络往返次数,并降低 GET 操作和索引操作之间发生版本冲突的可能性。

必须启用 _source 字段才能使用 update。除了 _source 之外,您还可以通过 ctx 映射访问以下变量:_index_type_id_version_routing_now(当前时间戳)。

路径参数编辑

<index>
(必需,字符串) 目标索引的名称。默认情况下,如果索引不存在,则会自动创建它。有关更多信息,请参见 自动创建数据流和索引
<_id>
(必需,字符串) 要更新的文档的唯一标识符。

查询参数编辑

if_seq_no
(可选,整数) 仅当文档具有此序列号时才执行操作。请参见 乐观并发控制
if_primary_term
(可选,整数) 仅当文档具有此主项时才执行操作。请参见 乐观并发控制
lang
(可选,字符串) 脚本语言。默认值:painless
require_alias
(可选,布尔值) 如果为 true,则目标必须是 索引别名。默认为 false
refresh
(可选,枚举) 如果为 true,则 Elasticsearch 会刷新受影响的分片,以使此操作对搜索可见;如果为 wait_for,则等待刷新,以使此操作对搜索可见;如果为 false,则不执行刷新操作。有效值:truefalsewait_for。默认值:false
retry_on_conflict
(可选,整数) 指定发生冲突时应重试操作的次数。默认值:0。
routing
(可选,字符串) 用于将操作路由到特定分片的自定义值。
_source
(可选,列表) 设置为 false 以禁用源检索(默认值:true)。您还可以指定要检索的字段的逗号分隔列表。
_source_excludes
(可选,列表) 指定要排除的源字段。
_source_includes
(可选,列表) 指定要检索的源字段。
timeout

(可选,时间单位) 等待以下操作的时间段

默认为 1m(一分钟)。这保证 Elasticsearch 在失败之前至少等待超时时间。实际等待时间可能更长,尤其是在发生多次等待时。

wait_for_active_shards

(可选,字符串) 在继续操作之前必须处于活动状态的分片副本数量。设置为 all 或任何正整数,直到索引中的分片总数(number_of_replicas+1)。默认值:1,主分片。

请参见 活动分片

示例编辑

首先,让我们索引一个简单的文档

resp = client.index(
    index="test",
    id="1",
    body={"counter": 1, "tags": ["red"]},
)
print(resp)
response = client.index(
  index: 'test',
  id: 1,
  body: {
    counter: 1,
    tags: [
      'red'
    ]
  }
)
puts response
res, err := es.Index(
	"test",
	strings.NewReader(`{
	  "counter": 1,
	  "tags": [
	    "red"
	  ]
	}`),
	es.Index.WithDocumentID("1"),
	es.Index.WithPretty(),
)
fmt.Println(res, err)
PUT test/_doc/1
{
  "counter" : 1,
  "tags" : ["red"]
}

要递增计数器,您可以使用以下脚本提交更新请求

resp = client.update(
    index="test",
    id="1",
    body={
        "script": {
            "source": "ctx._source.counter += params.count",
            "lang": "painless",
            "params": {"count": 4},
        }
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: {
      source: 'ctx._source.counter += params.count',
      lang: 'painless',
      params: {
        count: 4
      }
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": {
	    "source": "ctx._source.counter += params.count",
	    "lang": "painless",
	    "params": {
	      "count": 4
	    }
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script" : {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params" : {
      "count" : 4
    }
  }
}

类似地,您可以使用更新脚本将标签添加到标签列表中(这只是一个列表,因此即使标签存在也会添加它)

resp = client.update(
    index="test",
    id="1",
    body={
        "script": {
            "source": "ctx._source.tags.add(params.tag)",
            "lang": "painless",
            "params": {"tag": "blue"},
        }
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: {
      source: 'ctx._source.tags.add(params.tag)',
      lang: 'painless',
      params: {
        tag: 'blue'
      }
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": {
	    "source": "ctx._source.tags.add(params.tag)",
	    "lang": "painless",
	    "params": {
	      "tag": "blue"
	    }
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script": {
    "source": "ctx._source.tags.add(params.tag)",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

您还可以从标签列表中删除标签。用于 remove 标签的 Painless 函数接受要删除的元素的数组索引。为了避免可能的运行时错误,您首先需要确保标签存在。如果列表包含标签的重复项,此脚本只会删除一个出现。

resp = client.update(
    index="test",
    id="1",
    body={
        "script": {
            "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
            "lang": "painless",
            "params": {"tag": "blue"},
        }
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: {
      source: 'if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }',
      lang: 'painless',
      params: {
        tag: 'blue'
      }
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": {
	    "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
	    "lang": "painless",
	    "params": {
	      "tag": "blue"
	    }
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

您还可以向文档添加和删除字段。例如,此脚本添加了字段 new_field

resp = client.update(
    index="test",
    id="1",
    body={"script": "ctx._source.new_field = 'value_of_new_field'"},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: "ctx._source.new_field = 'value_of_new_field'"
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": "ctx._source.new_field = 'value_of_new_field'"
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script" : "ctx._source.new_field = 'value_of_new_field'"
}

相反,此脚本删除了字段 new_field

resp = client.update(
    index="test",
    id="1",
    body={"script": "ctx._source.remove('new_field')"},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: "ctx._source.remove('new_field')"
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": "ctx._source.remove('new_field')"
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script" : "ctx._source.remove('new_field')"
}

以下脚本从对象字段中删除子字段

resp = client.update(
    index="test",
    id="1",
    body={"script": "ctx._source['my-object'].remove('my-subfield')"},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: "ctx._source['my-object'].remove('my-subfield')"
  }
)
puts response
POST test/_update/1
{
  "script": "ctx._source['my-object'].remove('my-subfield')"
}

您还可以更改从脚本中执行的操作,而不是更新文档。例如,此请求如果 tags 字段包含 green,则会删除文档,否则什么也不做(noop

resp = client.update(
    index="test",
    id="1",
    body={
        "script": {
            "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'noop' }",
            "lang": "painless",
            "params": {"tag": "green"},
        }
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: {
      source: "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'noop' }",
      lang: 'painless',
      params: {
        tag: 'green'
      }
    }
  }
)
puts response
POST test/_update/1
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'noop' }",
    "lang": "painless",
    "params": {
      "tag": "green"
    }
  }
}
更新文档的一部分编辑

以下部分更新将新字段添加到现有文档中

resp = client.update(
    index="test",
    id="1",
    body={"doc": {"name": "new_name"}},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    doc: {
      name: 'new_name'
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "doc": {
	    "name": "new_name"
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "doc": {
    "name": "new_name"
  }
}

如果同时指定了 docscript,则会忽略 doc。如果您指定了脚本更新,请在脚本中包含要更新的字段。

检测无操作更新编辑

默认情况下,不会更改任何内容的更新会检测到它们不会更改任何内容,并返回 "result": "noop"

resp = client.update(
    index="test",
    id="1",
    body={"doc": {"name": "new_name"}},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    doc: {
      name: 'new_name'
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "doc": {
	    "name": "new_name"
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "doc": {
    "name": "new_name"
  }
}

如果 name 的值已经是 new_name,则会忽略更新请求,并且响应中的 result 元素将返回 noop

{
   "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
   },
   "_index": "test",
   "_id": "1",
   "_version": 2,
   "_primary_term": 1,
   "_seq_no": 1,
   "result": "noop"
}

您可以通过将 "detect_noop": false 设置为禁用此行为

resp = client.update(
    index="test",
    id="1",
    body={"doc": {"name": "new_name"}, "detect_noop": False},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    doc: {
      name: 'new_name'
    },
    detect_noop: false
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "doc": {
	    "name": "new_name"
	  },
	  "detect_noop": false
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "doc": {
    "name": "new_name"
  },
  "detect_noop": false
}
Upsert编辑

如果文档不存在,则会将 upsert 元素的内容插入为新文档。如果文档存在,则会执行 script

resp = client.update(
    index="test",
    id="1",
    body={
        "script": {
            "source": "ctx._source.counter += params.count",
            "lang": "painless",
            "params": {"count": 4},
        },
        "upsert": {"counter": 1},
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    script: {
      source: 'ctx._source.counter += params.count',
      lang: 'painless',
      params: {
        count: 4
      }
    },
    upsert: {
      counter: 1
    }
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "script": {
	    "source": "ctx._source.counter += params.count",
	    "lang": "painless",
	    "params": {
	      "count": 4
	    }
	  },
	  "upsert": {
	    "counter": 1
	  }
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "script": {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params": {
      "count": 4
    }
  },
  "upsert": {
    "counter": 1
  }
}
脚本化 upsert编辑

要无论文档是否存在都运行脚本,请将 scripted_upsert 设置为 true

resp = client.update(
    index="test",
    id="1",
    body={
        "scripted_upsert": True,
        "script": {
            "source": "\n      if ( ctx.op == 'create' ) {\n        ctx._source.counter = params.count\n      } else {\n        ctx._source.counter += params.count\n      }\n    ",
            "params": {"count": 4},
        },
        "upsert": {},
    },
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    scripted_upsert: true,
    script: {
      source: "\n      if ( ctx.op == 'create' ) {\n        ctx._source.counter = params.count\n      } else {\n        ctx._source.counter += params.count\n      }\n    ",
      params: {
        count: 4
      }
    },
    upsert: {}
  }
)
puts response
POST test/_update/1
{
  "scripted_upsert": true,
  "script": {
    "source": """
      if ( ctx.op == 'create' ) {
        ctx._source.counter = params.count
      } else {
        ctx._source.counter += params.count
      }
    """,
    "params": {
      "count": 4
    }
  },
  "upsert": {}
}
文档作为 upsert编辑

您可以将 doc_as_upsert 设置为 true,以使用 doc 的内容作为 upsert 值,而不是发送部分 doc 加上 upsert 文档。

resp = client.update(
    index="test",
    id="1",
    body={"doc": {"name": "new_name"}, "doc_as_upsert": True},
)
print(resp)
response = client.update(
  index: 'test',
  id: 1,
  body: {
    doc: {
      name: 'new_name'
    },
    doc_as_upsert: true
  }
)
puts response
res, err := es.Update(
	"test",
	"1",
	strings.NewReader(`{
	  "doc": {
	    "name": "new_name"
	  },
	  "doc_as_upsert": true
	}`),
	es.Update.WithPretty(),
)
fmt.Println(res, err)
POST test/_update/1
{
  "doc": {
    "name": "new_name"
  },
  "doc_as_upsert": true
}

不支持将 摄取管道doc_as_upsert 结合使用。