• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Elasticsearch:从实例中学习 nested 数据类型的 CRUD 及搜索

武飞扬头像
Elasticsearch
帮助66

前言

nested 数据类型是一个比较高级的话题。在本文中,将介绍 Elasticsearch 中针对嵌套对象的一些高级 CRUD 和搜索查询。 如果你想了解有关 Elasticsearch 基础知识的更多信息

在进行下面的练习之前,建议阅读上面的两篇文章中的任何一篇以建立好 laptops-demo 这个索引。在完成之后,我们可以通过如下的命令来查看 laptops-demo 的 mappings 及 settings:

GET laptops-demo

上面的命令返回结果:

`

1.  {
2.    "laptops-demo": {
3.      "aliases": {},
4.      "mappings": {
5.        "properties": {
6.          "attributes": {
7.            "type": "nested",
8.            "properties": {
9.              "attribute_name": {
10.                "type": "text"
11.              },
12.              "attribute_value": {
13.                "type": "text"
14.              }
15.            }
16.          },
17.          "brand": {
18.            "type": "text",
19.            "fields": {
20.              "keyword": {
21.                "type": "keyword"
22.              }
23.            }
24.          },
25.          "id": {
26.            "type": "long"
27.          },
28.          "name": {
29.            "type": "text",
30.            "fields": {
31.              "keyword": {
32.                "type": "keyword"
33.              },
34.              "ngrams": {
35.                "type": "text",
36.                "analyzer": "ngram_analyzer"
37.              }
38.            },
39.            "analyzer": "standard"
40.          },
41.          "price": {
42.            "type": "float"
43.          }
44.        }
45.      },
46.      "settings": {
47.        "index": {
48.          "routing": {
49.            "allocation": {
50.              "include": {
51.                "_tier_preference": "data_content"
52.              }
53.            }
54.          },
55.          "number_of_shards": "1",
56.          "provided_name": "laptops-demo",
57.          "creation_date": "1673943360770",
58.          "analysis": {
59.            "filter": {
60.              "ngram_filter": {
61.                "type": "edge_ngram",
62.                "min_gram": "2",
63.                "max_gram": "15"
64.              }
65.            },
66.            "analyzer": {
67.              "ngram_analyzer": {
68.                "filter": [
69.                  "lowercase",
70.                  "ngram_filter"
71.                ],
72.                "type": "custom",
73.                "tokenizer": "standard"
74.              }
75.            }
76.          },
77.          "number_of_replicas": "1",
78.          "uuid": "kWCIC0tUTJKrL3gUE1fGcA",
79.          "version": {
80.            "created": "8060099"
81.          }
82.        }
83.      }
84.    }
85.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

从返回的 mappings 的结果中,我们可以看出来:

 1.        "properties": {
2.          "attributes": {
3.            "type": "nested",
4.            "properties": {
5.              "attribute_name": {
6.                "type": "text"
7.              },
8.              "attribute_value": {
9.                "type": "text"
10.              }
11.            }
12.          }

在此示例中,attributes 字段是一个 nested 字段,包含 attribute_name 和 attribure_value 字段作为子字段。 nested 类型是 object 数据类型的特殊版本,它允许对象数组以一种可以彼此独立查询的方式进行索引。 我们应该始终为 nested 字段创建一个映射,如本示例所示,因为 Elasticsearch 没有内部对象的概念,并且会将对象层次结构扁平化为字段名称和值的简单列表,这通常不是我们想要的。更多关于 nested 数据类型的介绍,可以参考文章 “Elasticsearch: object 及 nested 数据类型”。

要创建包含 nested 对象的文档,请运行以下命令:

`

1.  PUT laptops-demo/_doc/1000
2.  {
3.    "id": 1,
4.    "name": "HP EliteBook Model 1",
5.    "price": 38842,
6.    "brand": "HP",
7.    "attributes": [
8.      {
9.        "attribute_name": "cpu",
10.        "attribute_value": "Intel Core i7"
11.      },
12.      {
13.        "attribute_name": "memory",
14.        "attribute_value": "8GB"
15.      },
16.      {
17.        "attribute_name": "storage",
18.        "attribute_value": "256GB"
19.      }
20.    ]
21.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

如上所示,我们针对 attributes 字段写入我们想要的数组。

查看刚刚创建的文档:

GET laptops-demo/_doc/1000
`

1.  {
2.    "_index": "laptops-demo",
3.    "_id": "1000",
4.    "_version": 1,
5.    "_seq_no": 201,
6.    "_primary_term": 2,
7.    "found": true,
8.    "_source": {
9.      "id": 1,
10.      "name": "HP EliteBook Model 1",
11.      "price": 38842,
12.      "brand": "HP",
13.      "attributes": [
14.        {
15.          "attribute_name": "cpu",
16.          "attribute_value": "Intel Core i7"
17.        },
18.        {
19.          "attribute_name": "memory",
20.          "attribute_value": "8GB"
21.        },
22.        {
23.          "attribute_name": "storage",
24.          "attribute_value": "256GB"
25.        }
26.      ]
27.    }
28.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

有些开发者可能要问为啥需要 nested 这个数据类型呢?假如我们有另外一个文档:

`

1.  PUT laptops-demo/_doc/1001
2.  {
3.    "id": 1,
4.    "name": "HP EliteBook Model 2",
5.    "price": 40000,
6.    "brand": "Apple",
7.    "attributes": [
8.      {
9.        "attribute_name": "cpu",
10.        "attribute_value": "Intel Core i7"
11.      },
12.      {
13.        "attribute_name": "memory",
14.        "attribute_value": "256GB"
15.      },
16.      {
17.        "attribute_name": "storage",
18.        "attribute_value": "8GB"
19.      }
20.    ]
21.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

在这里,我们把 id = 1001 里的 memory 和 storage 里的内存值和之前的文档 id = 1000 的对调了一下。

如果在没有定义 nested 数据类型的情况下,写入上面的两个文档,并做如下的查询:

`

1.  GET laptops-demo/_search
2.  {
3.    "query": {
4.      "bool": {
5.        "must": [
6.          {
7.            "match": {
8.              "attributes.attribute_name": "memory"
9.            }
10.          },
11.          {
12.            "match": {
13.              "attributes.attribute_value": "8GB"
14.            }
15.          }
16.        ]
17.      }
18.    }
19.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

那么返回的结果将是两个文档被同时搜索到。显然这个不是我们所需要的结果,因为我们需要的结果是 memory 的内存值为 8GB,而不是 256GB 的电脑。这个是因为当 JSON 对象被 Lucene 扁平化后,我们失去了 attribute_name 和 attribute_value 之间的对应关系。取而代之的是如下的这种关系:



1.  {
2.    ...
3.    "attributes.attribute_name :["memory", "storage"],
4.    "attributes.attribute_value": ["8GB", "256GB"]
5.  }


很显然,对上面的两个文档来说,他们的搜索结果都是一样的。只有我们使用 nested 数据类型,我们可以让它们的关系变得一一对应起来。

假设我们要在attributes 字段中添加一个新的属性,应该怎么做呢? 事实证明,nested 字段属性实际上是一个数组,我们可以像这样向其中添加更多对象:



1.  POST laptops-demo/_update/1000
2.  {
3.    "script": {
4.      "source": "ctx._source.attributes.add(params.attribute)",
5.      "params": {
6.        "attribute": {
7.          "attribute_name": "screen_size",
8.          "attribute_value": "13 inch"
9.        }
10.      }
11.    }
12.  }


正如演示的那样,nested 字段可以通过 ctx._source.attributes 访问,它作为数组返回。 我们可以通过 add 方法向这个数组中添加一个新对象。

查看文档,会发现在文档的attributes字段中增加了一个 id 为 1000 的新属性。

GET laptops-demo/_doc/1000
`

1.  {
2.    "_index": "laptops-demo",
3.    "_id": "1000",
4.    "_version": 3,
5.    "_seq_no": 205,
6.    "_primary_term": 2,
7.    "found": true,
8.    "_source": {
9.      "id": 1,
10.      "name": "HP EliteBook Model 1",
11.      "price": 38842,
12.      "brand": "HP",
13.      "attributes": [
14.        {
15.          "attribute_name": "cpu",
16.          "attribute_value": "Intel Core i7"
17.        },
18.        {
19.          "attribute_name": "memory",
20.          "attribute_value": "8GB"
21.        },
22.        {
23.          "attribute_name": "storage",
24.          "attribute_value": "256GB"
25.        },
26.        {
27.          "attribute_name": "screen_size",
28.          "attribute_value": "13 inch"
29.        }
30.      ]
31.    }
32.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

如果我们想更新多个文档的 nested 字段,我们可以使用 _update_by_query 端点:

`

1.  POST laptops-demo/_update_by_query
2.  {
3.    "script": {
4.      "source": "ctx._source.attributes.add(params.attribute)",
5.      "params": {
6.        "attribute": {
7.          "attribute_name": "screen_size",
8.          "attribute_value": "13 inch"
9.        }
10.      }
11.    },
12.    "query": {
13.      "term": {
14.        "brand.keyword": {
15.          "value": "Apple"
16.        }
17.      }
18.    }
19.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

要找出所有刚刚更新的 Apple MacBooks:



1.  GET laptops-demo/_search
2.  {
3.    "query": {
4.      "term": {
5.        "brand.keyword": {
6.          "value": "Apple"
7.        }
8.      }
9.    }
10.  }


>如果想从 nested 字段中删除对象怎么办? 例如,如果我们要删除文档 1 的 screen_size 属性怎么办? 在这种情况下,我们需要一些编程逻辑。 我们需要先找到要移除的具体对象,然后将其从数组中移除。



1.  POST laptops-demo/_update/1
2.  {
3.    "script": {
4.      "source": "ctx._source.attributes.removeIf(attr -> attr.attribute_name == params.attribute_name)",
5.      "params": {
6.        "attribute_name": "screen_size"
7.      }
8.    }
9.  }


removeIf 方法删除匿名函数找到的对象。

另一个用例是如何更新 nested 对象中的特定字段。 例如,当 attribute_name 为 “memory” 时,如果我们只想更新 attribute_value 怎么办? 在这种情况下,我们需要使用 painless 脚本语言的更多编程逻辑:

`

1.  POST laptops-demo/_update/1
2.  {
3.    "script": {
4.      "source": """
5.        def attributes = ctx._source.attributes.findAll(
6.          attr -> attr.attribute_name == params.attribute_name
7.        );
8.        for (attr in attributes) {
9.          attr.attribute_value = params.new_attribute_value
10.        }
11.      """,
12.      "params": {
13.        "attribute_name": "memory",
14.        "new_attribute_value": "16GB"
15.      }
16.    }
17.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

逻辑是先用 findAll 方法找到所有相关的 nested 对象,然后用 for 循环一个一个更新。

同样,如果你想更新满足某些条件的多个文档,你可以使用 _update_by_query 端点,如上所示。

最后,我想介绍一下如何查询 nested 字段。 当你第一次看到查询时,它可能会非常吓人。 但是,如果你掌握了模式,你就不会被它吓到,可以在工作中自由使用。

要查询 nested 字段,我们需要在 nested 查询中使用布尔查询。 让我们首先尝试找到所有 memory 为 8GB 的笔记本电脑。

`

1.  GET laptops-demo/_search
2.  {
3.    "query": {
4.      "nested": {
5.        "path": "attributes",
6.        "query": {
7.          "bool": {
8.            "must": [
9.              { "match": { "attributes.attribute_name": "memory" }},
10.              { "match": { "attributes.attribute_value":  "16GB" }} 
11.            ]
12.          }
13.        }
14.      }
15.    }
16.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

关键点

  • nested 关键字指定我们正在查询 nested 字段。
  • path 指定 nested 字段的名称,在本例中为 attributes。
  • bool 表示我们正在使用布尔查询,因为我们希望 attribute_name 和 attribute_value 字段都满足某些条件。
  • must 表示子查询必须全部出现在文档中。
  • match 表示全文搜索,因为 attribute_name 和 attribute_value 字段都是 text 字段。 如果其中一些不是 text 字段,我们需要使用 term、terms 或 range 查询。

现在让我们介绍一个更复杂的。 让我们找出所有 HP 笔记本电脑,其 memory 为 8GB,storage 为 256GB:

`

1.  GET laptops-demo/_search
2.  {
3.    "query": {
4.      "bool": {
5.        "must": [
6.          {
7.            "term": {
8.              "brand.keyword": {
9.                "value": "HP"
10.              }
11.            }
12.          },
13.          {
14.            "nested": {
15.              "path": "attributes",
16.              "query": {
17.                "bool": {
18.                  "must": [
19.                    {
20.                      "match": {
21.                        "attributes.attribute_name": "memory"
22.                      }
23.                    },
24.                    {
25.                      "match": {
26.                        "attributes.attribute_value": "8GB"
27.                      }
28.                    }
29.                  ]
30.                }
31.              }
32.            }
33.          },
34.          {
35.            "nested": {
36.              "path": "attributes",
37.              "query": {
38.                "bool": {
39.                  "must": [
40.                    {
41.                      "match": {
42.                        "attributes.attribute_name": "storage"
43.                      }
44.                    },
45.                    {
46.                      "match": {
47.                        "attributes.attribute_value": "256GB"
48.                      }
49.                    }
50.                  ]
51.                }
52.              }
53.            }
54.          }
55.        ]
56.      }
57.    }
58.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

在这个大查询中,外部 bool 查询指定了三个应该满足的条件:

  • 第一个条件是匹配 brand 字段,必须是 HP。
  • 第二个条件是 nested 查询,它包装另一个查询以搜索 nested 属性字段。 在内部 bool 查询中,我们要求 attribute_name 必须是 “memory”,attribute_value必须是 “8G”。 两者都应该满足。
  • 同样,第三个条件也是 nested 查询,要求 attribute_name 必须为 “storage”,attribute_value 必须为 “256GB”。

如果你知道模式,就不会那么复杂,不是吗? 😃通过这个查询,我们可以得到所有brand 为 HP、内存为 8GB、存储空间为 256GB 的笔记本电脑。

总结

在本文中,我们简要介绍了常见的查询以创建、读取、更新、删除和搜索嵌套查询。 对于 CRUD 操作,你需要编写一个简单的 painless 脚本。 你无需掌握整个painless 编程语言即可使用 Elasticsearch。 了解一些常用命令就足以满足日常使用。

对于 nested 字段搜索,不要被看似复杂的查询吓倒。 只要你了解 bool 和 nested 查询的工作原理,就可以使用 bool 和 nested 查询作为构建块来自行构建强大的查询。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfbffe
系列文章
更多 icon
同类精品
更多 icon
继续加载