Elasticsearch使用:嵌套对象

2021-10-13 17:52:09 浏览数 (1)

简介

官网地址:https://www.elastic.co/guide/cn/elasticsearch/guide/current/nested-objects.html

由于在 Elasticsearch 中单个文档的增删改都是原子性操作,那么将相关实体数据都存储在同一文档中也就理所当然。 比如说,我们可以将订单及其明细数据存储在一个文档中。又比如,我们可以将一篇博客文章的评论以一个 comments 数组的形式和博客文章放在一起:

代码语言:javascript复制
PUT /my_index/blogpost/1
{
  "title": "Nest eggs",
  "body":  "Making your money work...",
  "tags":  [ "cash", "shares" ],
  "comments": [   # 如果我们依赖字段自动映射,那么 comments 字段会自动映射为 object 类型。
    {
      "name":    "John Smith",
      "comment": "Great article",
      "age":     28,
      "stars":   4,
      "date":    "2014-09-01"
    },
    {
      "name":    "Alice White",
      "comment": "More like this please",
      "age":     31,
      "stars":   5,
      "date":    "2014-10-22"
    }
  ]
}

由于所有的信息都在一个文档中,当我们查询时就没有必要去联合文章和评论文档,查询效率就很高。

但是当我们使用如下查询时,上面的文档也会被当做是符合条件的结果:

代码语言:javascript复制
GET /_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "Alice" }},
        { "match": { "age":  28      }}   # Alice实际是31岁,不是28!
      ]
    }
  }
}

正如我们在 对象数组 中讨论的一样,出现上面这种问题的原因是 JSON 格式的文档被处理成如下的扁平式键值对的结构。

代码语言:javascript复制
{
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ],
  "comments.name":    [ alice, john, smith, white ],
  "comments.comment": [ article, great, like, more, please, this ],
  "comments.age":     [ 28, 31 ],
  "comments.stars":   [ 4, 5 ],
  "comments.date":    [ 2014-09-01, 2014-10-22 ]
}

Alice 和 31 、 John2014-09-01 之间的相关性信息不再存在。虽然 object 类型 (参见 内部对象) 在存储 单一对象 时非常有用,但对于对象数组的搜索而言,毫无用处。

嵌套对象 就是来解决这个问题的。comments 字段类型设置为 nested 而不是 object 后,每一个嵌套对象都会被索引为一个 隐藏的独立文档 ,举例如下:

代码语言:javascript复制
{ # 第一个 嵌套文档
  "comments.name":    [ john, smith ],  
  "comments.comment": [ article, great ],
  "comments.age":     [ 28 ],
  "comments.stars":   [ 4 ],
  "comments.date":    [ 2014-09-01 ]
}
{ #	第二个 嵌套文档
  "comments.name":    [ alice, white ],
  "comments.comment": [ like, more, please, this ],
  "comments.age":     [ 31 ],
  "comments.stars":   [ 5 ],
  "comments.date":    [ 2014-10-22 ]
}
{ # 根文档 或者也可称为父文档
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ]
}

在独立索引每一个嵌套对象后,对象中每个字段的相关性得以保留。我们查询时,也仅仅返回那些真正符合条件的文档。

不仅如此,由于嵌套文档直接存储在文档内部,查询时嵌套文档和根文档联合成本很低,速度和单独存储几乎一样。

嵌套文档是隐藏存储的,我们不能直接获取。如果要增删改一个嵌套对象,我们必须把整个文档重新索引才可以。值得注意的是,查询的时候返回的是整个文档,而不是嵌套文档本身。

嵌套对象映射

设置一个字段为 nested 很简单 —  你只需要将字段类型 object 替换为 nested

代码语言:javascript复制
PUT /my_index
{
  "mappings": {
    "blogpost": {
      "properties": {
        "comments": {
          "type": "nested",   # nested 字段类型的设置参数与 object 相同。
          "properties": {
            "name":    { "type": "string"  },
            "comment": { "type": "string"  },
            "age":     { "type": "short"   },
            "stars":   { "type": "short"   },
            "date":    { "type": "date"    }
          }
        }
      }
    }
  }
}

这就是需要设置的一切。至此,所有 comments 对象会被索引在独立的嵌套文档中。可以查看 nested 类型参考文档 获取更多详细信息。

嵌套对象查询

由于嵌套对象 被索引在独立隐藏的文档中,我们无法直接查询它们。 相应地,我们必须使用 nested 查询 去获取它们。

代码语言:javascript复制
GET /my_index/blogpost/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "eggs"   # title 子句是查询根文档的。
          }
        },
        {
          "nested": {
            "path": "comments",   # nested 子句作用于嵌套字段 comments 。在此查询中,既不能查询根文档字段,也不能查询其他嵌套文档。
            "query": {
              "bool": {
                "must": [   # comments.name 和 comments.age 子句操作在同一个嵌套文档中。
                  {
                    "match": {
                      "comments.name": "john"
                    }
                  },
                  {
                    "match": {
                      "comments.age": 28
                    }
                  }
                ]
              }
            }
          }
        }
      ]
}}}

nested 字段可以包含其他的 nested 字段。同样地,nested 查询也可以包含其他的nested 查询。而嵌套的层次会按照你所期待的被应用。

nested 查询肯定可以匹配到多个嵌套的文档。每一个匹配的嵌套文档都有自己的相关度得分,但是这众多的分数最终需要汇聚为可供根文档使用的一个分数。

默认情况下,根文档的分数是这些嵌套文档分数的平均值。可以通过设置 score_mode 参数来控制这个得分策略,相关策略有 avg (平均值), max (最大值), sum (加和) 和 none (直接返回 1.0 常数值分数)。

代码语言:javascript复制
GET /my_index/blogpost/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "eggs"
          }
        },
        {
          "nested": {
            "path": "comments",
            "score_mode": "max",   # 返回最优匹配嵌套文档的 _score 给根文档使用。
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "john"
                    }
                  },
                  {
                    "match": {
                      "comments.age": 28
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

如果 nested 查询放在一个布尔查询的 filter 子句中,其表现就像一个 nested 查询,只是score_mode 参数不再生效。因为它被用于不打分的查询中 — 只是符合或不符合条件,不必打分 — 那么score_mode 就没有任何意义,因为根本就没有要打分的地方。

使用嵌套字段排序

尽管嵌套字段的值存储于独立的嵌套文档中,但依然有方法按照嵌套字段的值排序。 让我们添加另一个记录,以使得结果更有意思:

代码语言:javascript复制
PUT /my_index/blogpost/2
{
  "title": "Investment secrets",
  "body":  "What they don't tell you ...",
  "tags":  [ "shares", "equities" ],
  "comments": [
    {
      "name":    "Mary Brown",
      "comment": "Lies, lies, lies",
      "age":     42,
      "stars":   1,
      "date":    "2014-10-18"
    },
    {
      "name":    "John Smith",
      "comment": "You're making it up!",
      "age":     28,
      "stars":   2,
      "date":    "2014-10-16"
    }
  ]
}

假如我们想要查询在10月份收到评论的博客文章,并且按照 stars 数的最小值来由小到大排序,那么查询语句如下:

代码语言:javascript复制
GET /_search
{
  "query": {
    "nested": {   # 此处的 nested 查询将结果限定为在10月份收到过评论的博客文章。
      "path": "comments",
      "filter": {
        "range": {
          "comments.date": {
            "gte": "2014-10-01",
            "lt":  "2014-11-01"
          }
        }
      }
    }
  },
  "sort": {
    "comments.stars": {   # 结果按照匹配的评论中 comment.stars 字段的最小值 (min) 来由小到大 (asc) 排序。
      "order": "asc",   # 结果按照匹配的评论中 comment.stars 字段的最小值 (min) 来由小到大 (asc) 排序。
      "mode":  "min",   # 结果按照匹配的评论中 comment.stars 字段的最小值 (min) 来由小到大 (asc) 排序。
      "nested_path": "comments",  # 排序子句中的 nested_path 和 nested_filter 和 query 子句中的 nested 查询相同,原因在下面有解释。
      "nested_filter": {
        "range": {
          "comments.date": {
            "gte": "2014-10-01",
            "lt":  "2014-11-01"
          }
        }
      }
    }
  }
}

我们为什么要用 nested_path 和 nested_filter 重复查询条件呢?原因在于,排序发生在查询执行之后。 查询条件限定了在10月份收到评论的博客文档,但返回的是博客文档。如果我们不在排序子句中加入 nested_filter , 那么我们对博客文档的排序将基于博客文档的所有评论,而不是仅仅在10月份接收到的评论。

嵌套聚合

在查询的时候,我们使用 nested 查询就可以获取嵌套对象的信息。同理, nested 聚合允许我们对嵌套对象里的字段进行聚合操作。

代码语言:javascript复制
GET /my_index/blogpost/_search
{
  "size" : 0,
  "aggs": {
    "comments": {   # nested 聚合 “进入” 嵌套的 comments 对象。
      "nested": {
        "path": "comments"
      },
      "aggs": {
        "by_month": {
          "date_histogram": {  # comment对象根据 comments.date 字段的月份值被分到不同的桶。
            "field":    "comments.date",
            "interval": "month",
            "format":   "yyyy-MM"
          },
          "aggs": {
            "avg_stars": { # 计算每个桶内star的平均数量。
              "avg": { 
                "field": "comments.stars"
              }
            }
          }
        }
      }
    }
  }
}

从下面的结果可以看出聚合是在嵌套文档层面进行的:

代码语言:javascript复制
...
"aggregations": {
  "comments": {
     "doc_count": 4, 
     "by_month": {
        "buckets": [
           {
              "key_as_string": "2014-09",
              "key": 1409529600000,
              "doc_count": 1,   # 总共有4个 comments 对象 :1个对象在9月的桶里,3个对象在10月的桶里。
              "avg_stars": {
                 "value": 4
              }
           },
           {
              "key_as_string": "2014-10",
              "key": 1412121600000,
              "doc_count": 3,   # 总共有4个 comments 对象 :1个对象在9月的桶里,3个对象在10月的桶里。
              "avg_stars": {
                 "value": 2.6666666666666665
              }
           }
        ]
     }
  }
}
...

逆向嵌套聚合

nested 聚合 只能对嵌套文档的字段进行操作。 根文档或者其他嵌套文档的字段对它是不可见的。 然而,通过 reverse_nested 聚合,我们可以 走出 嵌套层级,回到父级文档进行操作。

例如,我们要基于评论者的年龄找出评论者感兴趣 tags 的分布。 comment.age 是一个嵌套字段,但 tags 在根文档中:

代码语言:javascript复制
GET /my_index/blogpost/_search
{
  "size" : 0,
  "aggs": {
    "comments": {
      "nested": {   # nested 聚合进入 comments 对象。
        "path": "comments"
      },
      "aggs": {
        "age_group": {
          "histogram": {  # histogram 聚合基于 comments.age 做分组,每10年一个分组。 
            "field":    "comments.age",
            "interval": 10
          },
          "aggs": {
            "blogposts": {
              "reverse_nested": {},   # reverse_nested 聚合退回根文档。
              "aggs": {
                "tags": {
                  "terms": {   # terms 聚合计算每个分组年龄段的评论者最常用的标签词。
                    "field": "tags"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

简略结果如下所示:

代码语言:javascript复制
..
"aggregations": {
  "comments": {
     "doc_count": 4,   # 一共有4条评论。
     "age_group": {
        "buckets": [
           {
              "key": 20, # 在20岁到30岁之间总共有两条评论。
              "doc_count": 2,  # 在20岁到30岁之间总共有两条评论。
              "blogposts": {
                 "doc_count": 2, # 这些评论包含在两篇博客文章中。
                 "tags": {
                    "doc_count_error_upper_bound": 0,
                    "buckets": [   # 在这些博客文章中最热门的标签是 shares、 cash、equities。
                       { "key": "shares",   "doc_count": 2 },
                       { "key": "cash",     "doc_count": 1 },
                       { "key": "equities", "doc_count": 1 }
                    ]
                 }
              }
           },
...

嵌套对象的使用时机

嵌套对象在只有一个主要实体时非常有用,这个主要实体包含有限个紧密关联但又不是很重要的实体,例如我们的blogpost 对象包含评论对象。 在基于评论的内容查找博客文章时,nested 查询有很大的用处,并且可以提供更快的查询效率。

嵌套模型的缺点如下:

  • 当对嵌套文档做增加、修改或者删除时,整个文档都要重新被索引。嵌套文档越多,这带来的成本就越大。
  • 查询结果返回的是整个文档,而不仅仅是匹配的嵌套文档。尽管目前有计划支持只返回根文档中最佳匹配的嵌套文档,但目前还不支持。

有时你需要在主文档和其关联实体之间做一个完整的隔离设计。这个隔离是由 父子关联 提供的。

实例分享

嵌套文档看似与文档内有一个集合字段类似,但是实则有很大区别,以上面图中嵌套文档为例,留言1,留言2,留言3虽然都在当前文章所在的文档内,但是在内部其实存储为4个独立文档,如下图所示。

同时,嵌套文档的字段类型需要设置为nested,设置成nested后的不能被直接查询,需要使用nested查询。

新建索引

代码语言:javascript复制
{
  "title": "这是一篇文章",
  "body":  "这是一篇文章,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-04"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-04"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

创建索引名和type均为blog的索引,其中comments字段为嵌套文档类型,需要将type设置为nested,其余都是一些正常的字段
代码语言:javascript复制
设置Setting:

PUT /test_book
{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "index_ansj_analyzer": {
          "type": "custom",
          "tokenizer": "index_ansj",
          "filter": [
            "my_synonym",
            "asciifolding"
          ]
        },
        "comma": {
          "type": "pattern",
          "pattern": ","
        },
        "shingle_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "shingle_filter"
          ]
        }
      },
      "filter": {
        "my_synonym": {
          "type": "synonym",
          "synonyms_path": "analysis/synonym.txt"
        },
        "shingle_filter": {
          "type": "shingle",
          "min_shingle_size": 2,
          "max_shingle_size": 2,
          "output_unigrams": false
        }
      }
    }
  }
} 

设置Mapping:

PUT /test_book/_mapping/_doc
{
    "_doc":{
        "properties":{
            "comments":{
                "type":"nested",
                "properties":{
                    "date":{
                        "type":"date"
                    },
                    "name":{
                        "type":"text",
                        "fields":{
                            "keyword":{
                                "type":"keyword"
                            }
                        }
                    },
                    "comment":{
                        "type":"text",
                        "fields":{
                            "keyword":{
                                "type":"keyword"
                            }
                        }
                    },
                    "age":{
                        "type":"long"
                    }
                }
            },
            "body":{
                "type":"text",
                "fields":{
                    "keyword":{
                        "type":"keyword"
                    }
                }
            },
            "title":{
                "type":"text",
                "fields":{
                    "keyword":{
                        "type":"keyword"
                    }
                }
            }
        }
    }
}

索引数据

代码语言:javascript复制
PUT test_book/_doc/1
{
    "title":"这是一篇文章",
    "body":"这是一篇文章,从哪里说起呢? ... ...",
    "comments":[
        {
            "name":"张三",
            "comment":"写的不错",
            "age":28,
            "date":"2020-05-04"
        },
        {
            "name":"李四",
            "comment":"写的很好",
            "age":20,
            "date":"2020-05-04"
        },
        {
            "name":"王五",
            "comment":"这是一篇非常棒的文章",
            "age":31,
            "date":"2020-05-01"
        }
    ]
}

PUT test_book/_doc/2
{
  "title": "这是一篇文章2",
  "body":  "这是一篇文章2,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-11"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-16"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

PUT test_book/_doc/3
{
  "title": "这是一篇文章3",
  "body":  "这是一篇文章3,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-03"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-20"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

POST test_book/_search
{
  "query": {
    "match_all": {}
  }
}

结果:
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "test_book",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "这是一篇文章",
          "body" : "这是一篇文章,从哪里说起呢? ... ...",
          "comments" : [
            {
              "name" : "张三",
              "comment" : "写的不错",
              "age" : 28,
              "date" : "2020-05-04"
            },
            {
              "name" : "李四",
              "comment" : "写的很好",
              "age" : 20,
              "date" : "2020-05-04"
            },
            {
              "name" : "王五",
              "comment" : "这是一篇非常棒的文章",
              "age" : 31,
              "date" : "2020-05-01"
            }
          ]
        }
      }
    ]
  }
}

查询数据

代码语言:javascript复制
直接查询根文档:

POST test_book/_search
{
  "query": {
    "match": {
      "title.keyword": "这是一篇文章"
    }
  }
}

查询结果:
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "test_book",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "这是一篇文章",
          "body" : "这是一篇文章,从哪里说起呢? ... ...",
          "comments" : [
            {
              "name" : "张三",
              "comment" : "写的不错",
              "age" : 28,
              "date" : "2020-05-04"
            },
            {
              "name" : "李四",
              "comment" : "写的很好",
              "age" : 20,
              "date" : "2020-05-04"
            },
            {
              "name" : "王五",
              "comment" : "这是一篇非常棒的文章",
              "age" : 31,
              "date" : "2020-05-01"
            }
          ]
        }
      }
    ]
  }
}

备注:要是想直接查询内部文档是查询不到的,需要加上nested即可

POST test_book/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "文章"
          }
        },
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "张三"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

查询结果:
{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 2.5370226,
    "hits" : [
      {
        "_index" : "test_book",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 2.5370226,
        "_source" : {
          "title" : "这是一篇文章",
          "body" : "这是一篇文章,从哪里说起呢? ... ...",
          "comments" : [
            {
              "name" : "张三",
              "comment" : "写的不错",
              "age" : 28,
              "date" : "2020-05-04"
            },
            {
              "name" : "李四",
              "comment" : "写的很好",
              "age" : 20,
              "date" : "2020-05-04"
            },
            {
              "name" : "王五",
              "comment" : "这是一篇非常棒的文章",
              "age" : 31,
              "date" : "2020-05-01"
            }
          ]
        }
      }
    ]
  }
}

备注:nested中查询的是嵌套文档的内容,语法与正常查询时一致。 nested 查询肯定可以匹配到多个嵌套的文档。每一个匹配的嵌套文档都有自己的相关度得分,但是这众多的分数最终需要汇聚为可供根文档使用的一个分数。 默认情况下,根文档的分数是这些嵌套文档分数的平均值。可以通过设置 score_mode 参数来控制这个得分策略,相关策略有 avg(平均值),max(最大值),sum(加和) 和 none(直接返回 1.0 常数值分数)。

排序

代码语言:javascript复制
POST test_book/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "文章"
          }
        },
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "张三"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  },
  "sort": {
    "comments.date": {
      "order": "desc",
      "mode": "max",
      "nested_path": "comments",
      "nested_filter": {
        "bool": {
          "must": [
            {
              "match": {
                "comments.name": "张三"
              }
            }
          ]
        }
      }
    }
  }
}

备注:需要注意的是,在sort内,又添加了nested_filter来过滤一遍上面嵌套文档的查询条件,原因是这样的,在嵌套文档查询排序时是先按照条件进行查询,查询后再进行排序,那么可能由于数据的原因,导致排序的字段不是按照匹配上的数据进行排序

聚合

聚合的场景可能也比较常见,其实熟悉上面嵌套文档的使用的话,对聚合文档使用难度应该也不大。

代码语言:javascript复制
PUT test_book/_doc/4
{
  "title": "这是一篇文章4",
  "body":  "这是一篇文章4,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-03-03"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-04-20"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-06-01"
    }
  ]
}

举例:需要查询每个月评论人数的平均数,查询语句如下:

代码语言:javascript复制
POST test_book/_search
{
  "size": 0,
  "aggs": {
    "comments": {
      "nested": {
        "path": "comments"
      },
      "aggs": {
        "by_month": {
          "date_histogram": {
            "field": "comments.date",
            "interval": "month",
            "format": "yyyy-MM"
          },
          "aggs": {
            "avg_stars": {
              "avg": {
                "field": "comments.age"
              }
            }
          }
        }
      }
    }
  }
}

查询结果:
{
  "took" : 16,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "comments" : {
      "doc_count" : 12,
      "by_month" : {
        "buckets" : [
          {
            "key_as_string" : "2020-03",
            "key" : 1583020800000,
            "doc_count" : 1,
            "avg_stars" : {
              "value" : 28.0
            }
          },
          {
            "key_as_string" : "2020-04",
            "key" : 1585699200000,
            "doc_count" : 1,
            "avg_stars" : {
              "value" : 20.0
            }
          },
          {
            "key_as_string" : "2020-05",
            "key" : 1588291200000,
            "doc_count" : 9,
            "avg_stars" : {
              "value" : 26.333333333333332
            }
          },
          {
            "key_as_string" : "2020-06",
            "key" : 1590969600000,
            "doc_count" : 1,
            "avg_stars" : {
              "value" : 31.0
            }
          }
        ]
      }
    }
  }
}

备注:正如本文所说,嵌套文档中,所有内容都在同一个文档内,这就导致嵌套文档进行增加、修改或者删除时,整个文档都要重新被索引。嵌套文档越多,这带来的成本就越大。

0 人点赞