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,现在已经被成功地移除了。