Elasticsearch:如何修改 nested 字段的值

1,640 阅读3分钟

Nested 类型是 object 数据类型的特殊版本,它允许对象数组以一种可以彼此独立查询的方式进行索引。在内部,嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着每个嵌套对象都可以使用 nested query 独立于其他对象进行查询。每个 nested 对象都被索引为一个单独的 Lucene 文档。有关更多关于 nested 数据类型的文档,我们可以参考之前的文章 “Elasticsearch: object 及 nested 数据类型”。

在使用 Elasticsearch 时,为了系统的效率,我们并不建议经常修改文档,但是在有些时候,我们还必须对已经索引过的文档进行修改。针对 nested 类型的字段,我该如何进行更新及删除呢?

让我们先使用一个例子来进行展示。

我们首先来创建一个 developer 的索引:



1.  PUT developer
2.  {
3.    "mappings": {
4.      "properties": {
5.        "name": {
6.          "type": "text"
7.        },
8.        "skills": {
9.          "type": "nested",
10.          "properties": {
11.            "language": {
12.              "type": "keyword"
13.            },
14.            "level": {
15.              "type": "keyword"
16.            }
17.          }
18.        }
19.      }
20.    }
21.  }


在上面,我们定义 skills 为一个 nested 数据类型。我们使用如下的命令来创建两个文档:



1.  POST developer/_doc/101
2.  {
3.    "name": "zhang san",
4.    "skills": [
5.      {
6.        "language": "ruby",
7.        "level": "expert"
8.      },
9.      {
10.        "language": "javascript",
11.        "level": "beginner"
12.      }
13.     ]
14.  }

16.  POST developer/_doc/102
17.  {
18.    "name": "li si",
19.    "skills": [
20.      {
21.        "language": "ruby",
22.        "level": "beginner"
23.      }
24.     ]
25.  }


上面的命令写入了两个文档。

添加技能

针对第二个文档,我们想增加如下的一个技能:



1.  {
2.     "language": "Python",
3.      "level" "expert"
4.  }


首先让我们使用 painless 语言创建我们的脚本。 你可以在参考资料中阅读有关它的更多详细信息,但熟悉 Java 的人会发现编码很简单。关于 painless 语音的编程,你可以在文章 “Elastic:开发者上手指南” 中的 “Painless 编程” 章节中找到很多文章进行参考。

我们的脚本将验证 skills 字段是否为空,如果是,我们创建列表实例并稍后添加新项目。如果不是,则添加新 skills。 

 1.        if (ctx._source.skills != null) {
2.           ctx._source.skills.addAll(params.skills);
3.        } else {
4.          ctx._source.skills = new ArrayList();
5.          ctx._source.skills.addAll(params.skills);
6.        }

最终添加 skills 的代码是这样的:



1.  POST developer/_update/102
2.  {
3.    "script": {
4.      "source": """
5.        if (ctx._source.skills != null) {
6.          ctx._source.skills.addAll(params.skills);
7.        } else {
8.          ctx._source.skills = new ArrayList();
9.          ctx._source.skills.addAll(params.skills);
10.        }
11.      """,
12.      "params": {
13.        "skills": [
14.            {
15.              "language": "Python",
16.              "level": "expert"
17.            }
18.         ]
19.      }
20.    }
21.  }


我们通过如下的命令来进行验证:

GET developer/_doc/102

我们得到如下的结果:



1.  {
2.    "_index": "developer",
3.    "_id": "102",
4.    "_version": 3,
5.    "_seq_no": 4,
6.    "_primary_term": 1,
7.    "found": true,
8.    "_source": {
9.      "name": "li si",
10.      "skills": [
11.        {
12.          "language": "ruby",
13.          "level": "beginner"
14.        },
15.        {
16.          "level": "expert",
17.          "language": "Python"
18.        }
19.      ]
20.    }
21.  }


从上面,我们可以看出来新的 skills 已经被添加进去了。

删除 skills

同样,我们可以使用如下的代码来删除一个技能:



1.  POST developer/_update/102
2.  {
3.    "script": {
4.      "source": """
5.        if (ctx._source.skills != null) {
6.          for (int i; i < params.skills.length; i++) {
7.            ctx._source.skills.removeIf(a->
8.              a.language.equals(params.skills[i].language) &&
9.              a.level.equals(params.skills[i].level));
10.          }
11.        }
12.      """,
13.      "params": {
14.        "skills": [
15.          {
16.            "language": "Python",
17.            "level": "expert"
18.          }
19.        ]
20.      }
21.    }
22.  }


我们再次使用如下的命令来查看 id 为 102 的文档:

GET developer/_doc/102

上面的命令返回的值为:



1.  {
2.    "_index": "developer",
3.    "_id": "102",
4.    "_version": 4,
5.    "_seq_no": 5,
6.    "_primary_term": 1,
7.    "found": true,
8.    "_source": {
9.      "name": "li si",
10.      "skills": [
11.        {
12.          "language": "ruby",
13.          "level": "beginner"
14.        }
15.      ]
16.    }
17.  }


我们可以看出来,在上一步添加的 skill,现在已经被成功地移除了。