本文翻译自《How to manage nested objects in Elasticsearch documents》。
如何使用Update API和painless脚本在Elasticsearch文档中添加、更新和删除嵌套对象。
2019/05 /02
在这篇文章中,我们将管理使用Elasticsearch索引的文档的嵌套对象(nested objects)。
嵌套类型(nested)是对象类型(object)的一个特殊版本,它对对象数组进行索引,以使数组的元素可以被相互独立地查询。
先决条件
要跟随此帖子继续下去,你需要:
- 一个正在运行的Elasticsearch实例。我在这里用6.7版本
- 一个正在运行的Kibana实例,用于与Elasticsearch交互
准备
我们创建一个索引iridakos_nested_objects,它有一个名为human的类型(type)(Elasticsearch 8开始没有type这个概念了),其中有一个嵌套对象cats。
创建这个索引
打开Kibana开发控制台并键入以下内容以创建这个索引。
PUT iridakos_nested_objects
{
"mappings": {
"human": {
"properties": {
"name": {
"type": "text"
},
"cats": {
"type": "nested",
"properties": {
"colors": {
"type": "integer"
},
"name": {
"type": "text"
},
"breed": {
"type": "text"
}
}
}
}
}
}
}
// ES 8版本的语句
PUT iridakos_nested_objects
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"cats": {
"type": "nested",
"properties": {
"colors": {
"type": "integer"
},
"name": {
"type": "text"
},
"breed": {
"type": "text"
}
}
}
}
}
}
返回响应信息:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "iridakos_nested_objects"
}
human具有:
- text类型的name属性
- 嵌套类型(nested)的cats属性
每只cat都有:
- integer类型的colors属性
- text类型的name属性
- text类型的一个品种特性
添加一个human
在Kibana控制台中,执行以下操作以添加一个human和三只cat。
POST iridakos_nested_objects/human/1
{
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European"
},
{
"colors": 3,
"name": "Nino",
"breed": "Aegean"
}
]
}
// ES 8版本的语句
PUT iridakos_nested_objects/_doc/1
{
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European"
},
{
"colors": 3,
"name": "Nino",
"breed": "Aegean"
}
]
}
查询看看是否真的插入成功:
GET iridakos_nested_objects/human/1
// ES 8版本的语句
GET iridakos_nested_objects/_doc/1
你应该能看到类似以下的响应信息:
{
"_index": "iridakos_nested_objects",
"_type": "human",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European"
},
{
"colors": 3,
"name": "Nino",
"breed": "Aegean"
}
]
}
}
管理嵌套对象
添加一个新的嵌套对象
假设iridakos得到了一只新的波斯猫,名叫Leon。要将其添加到iridakos索引的cats集合中,我们将使用更新API。 在Kibana:
POST iridakos_nested_objects/human/1/_update
{
"script": {
"source": "ctx._source.cats.add(params.cat)",
"params": {
"cat": {
"colors": 4,
"name": "Leon",
"breed": "Persian"
}
}
}
}
// ES 8版本的语句
POST iridakos_nested_objects/_update/1
{
"script": {
"source": "ctx._source.cats.add(params.cat)",
"params": {
"cat": {
"colors": 4,
"name": "Leon",
"breed": "Persian"
}
}
}
}
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
注意事项:
- 我们使用ctx._source.cats访问了human的嵌套对象cats。这给了我们一个集合(collection)
- 我们对集合执行了add方法以添加一只新的cat
- 新cat(params.cat)作为参数传递给add方法,其属性在params中给出。
查询看看是否真的插入成功:
GET iridakos_nested_objects/human/1
// ES 8版本的语句
GET iridakos_nested_objects/_doc/1
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_type": "human",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European"
},
{
"colors": 3,
"name": "Nino",
"breed": "Aegean"
},
{
"colors": 4,
"name": "Leon",
"breed": "Persian"
}
]
}
}
可见,已插入Leon到cats集合。
删除一个嵌套对象
假设我们想把Nino从cats集合中移除。在Kibana:
POST iridakos_nested_objects/human/1/_update
{
"script": {
"source": "ctx._source.cats.removeIf(cat -> cat.name == params.cat_name)",
"params": {
"cat_name": "Nino"
}
}
}
// ES 8版本的语句
POST iridakos_nested_objects/_update/1
{
"script": {
"source": "ctx._source.cats.removeIf(cat -> cat.name == params.cat_name)",
"params": {
"cat_name": "Nino"
}
}
}
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
注意事项:
- 我们使用ctx._source.cats访问了嵌套对象cat。这给了我们一个集合(collection)
- 我们对集合执行removeIf方法,以有条件地移除里面的条目项
- 我们为removeIf方法提供了一个谓词(Predicate),在该方法中指定要删除的条目项。此谓词将在集合的每个条目项上执行,并返回为布尔值。如果返回true,则该条目项将被删除。在我们的例子中,条件是对cat的name属性进行简单的相等性检查。
- cat_name是作为参数(params.cat_name)传递的,而不是将其固定到source脚本里。
执行以下语句查看一下是否删除Nino成功:
GET iridakos_nested_objects/human/1
// ES 8版本的语句
GET iridakos_nested_objects/_doc/1
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_type": "human",
"_id": "1",
"_version": 3,
"found": true,
"_source": {
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European"
},
{
"colors": 4,
"name": "Leon",
"breed": "Persian"
}
]
}
}
可见name为Nino的cat信息已在cats集合中删除。
更新嵌套对象
假设我们想把所有的猫品种从欧洲(European)改为欧洲短毛猫(European Shorthair)(在我们的案例中目前只有Phoebe的品种是European)。
POST iridakos_nested_objects/human/1/_update
{
"script": {
"source": "def targets = ctx._source.cats.findAll(cat -> cat.breed == params.current_breed); for(cat in targets) { cat.breed = params.breed }",
"params": {
"current_breed": "European",
"breed": "European Shorthair"
}
}
}
// ES 8版本的语句
POST iridakos_nested_objects/_update/1
{
"script": {
"source": "def targets = ctx._source.cats.findAll(cat -> cat.breed == params.current_breed); for(cat in targets) { cat.breed = params.breed }",
"params": {
"current_breed": "European",
"breed": "European Shorthair"
}
}
}
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_id": "1",
"_version": 4,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
注意事项:
- 我们使用ctx._source.cats访问了嵌套对象cat。这给了我们一个集合(collection)
- 我们对cats集合执行了findAll方法来选择特定的条目项
- 我们为findAll方法提供了一个谓词(Predicate),在其中我们指定要选择的条目项。此谓词将在集合的每个条目项上执行,并返回布尔值。如果返回true,就会选择该条目项。在我们的例子中,条件是对猫的品种(breed)属性进行简单的相等性检查。
- current_breed作为参数(params.current_breed)传递,而不是将其固定到source脚本里。
- 然后,我们循环遍历选中的猫(其品种属性值为European),并将它们的品种更改为我们通过另一个参数params.breed传递的新值。
查看是否更新成功:
GET iridakos_nested_objects/human/1
// ES 8版本的语句
GET iridakos_nested_objects/_doc/1
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_type": "human",
"_id": "1",
"_version": 5,
"found": true,
"_source": {
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European Shorthair"
},
{
"colors": 4,
"name": "Leon",
"breed": "Persian"
}
]
}
}
可见更新成功。
更新满足多个条件的嵌套对象的多个属性
现在,在一个更高级的示例中,我们将使用一个更灵活的脚本来:
- 基于多种条件来匹配目标对象(此处为color和breed)
- 更新多个属性(此处为color和breed)
假设我们想改变有4种颜色的猫的品种,它们的品种从Persian到Aegean,它们的颜色改为3种。使用下面的脚本:
POST iridakos_nested_objects/human/1/_update
{
"script": {
"source": "def targets = ctx._source.cats.findAll(cat -> { for (condition in params.conditions.entrySet()) { if (cat[condition.getKey()] != condition.getValue()) { return false; } } return true; }); for (cat in targets) { for (change in params.changes.entrySet()) { cat[change.getKey()] = change.getValue() } }",
"params": {
"conditions": {
"breed": "Persian",
"colors": 4
},
"changes": {
"breed": "Aegean",
"colors": 3
}
}
}
}
// ES 8版本的语句
POST iridakos_nested_objects/_update/1
{
"script": {
"source": "def targets = ctx._source.cats.findAll(cat -> { for (condition in params.conditions.entrySet()) { if (cat[condition.getKey()] != condition.getValue()) { return false; } } return true; }); for (cat in targets) { for (change in params.changes.entrySet()) { cat[change.getKey()] = change.getValue() } }",
"params": {
"conditions": {
"breed": "Persian",
"colors": 4
},
"changes": {
"breed": "Aegean",
"colors": 3
}
}
}
}
为了方便起见,下面是带有适当缩进的脚本的源代码。
注意事项:
- 我们通过检查cat的属性是否具有params.conditions中指定的值来选择要更新的cat。
- 对于每个选定的cat,我们按照params.changes中给出的值更改它对应的属性值。
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_id": "1",
"_version": 5,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}
确认一下是否更新成功:
GET iridakos_nested_objects/human/1
// ES 8版本的语句
GET iridakos_nested_objects/_doc/1
返回响应信息:
{
"_index": "iridakos_nested_objects",
"_type": "human",
"_id": "1",
"_version": 5,
"found": true,
"_source": {
"name": "iridakos",
"cats": [
{
"colors": 1,
"name": "Irida",
"breed": "European Shorthair"
},
{
"colors": 2,
"name": "Phoebe",
"breed": "European Shorthair"
},
{
"name": "Leon",
"colors": 3,
"breed": "Aegean"
}
]
}
}
可见,更新成功。
参考
https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
https://www.elastic.co/guide/en/elasticsearch/painless/6.7/painless-api-reference.html