top_hits 聚合

top_hits 度量聚合器跟踪被聚集的最相关的文档。 这个聚合器被用作子聚合器,这样就可以对每个桶聚合最匹配的文档。

top_hits 聚合器可以有效地用于通过桶聚合器按特定字段对结果集进行分组。 一个或多个桶聚合器决定了将结果集划分到哪些属性中。

选项

  • from - 要获取的第一个结果的偏移量。
  • size - 每个桶返回的最大匹配命中数。默认情况下,会返回前3个匹配项。
  • sort - 命中应该如何排序。默认情况下,命中按主查询的得分排序。

单次命中(per hit)支持的功能

top_hits 聚合返回常规搜索命中,因为可以支持许多单次命中功能:

如果你需要docvalue_fieldssizesort,那么top_metrics 聚合可能是比 top_hits 聚合更有效率的选择。

示例

在下面的示例中,我们按类型对销售进行分组,并按类型显示最后一笔销售。 对于每笔销售,源中只包含日期和价格字段。

POST /sales/_search?size=0
{
    "aggs": {
        "top_tags": {
            "terms": {
                "field": "type",
                "size": 3
            },
            "aggs": {
                "top_sales_hits": {
                    "top_hits": {
                        "sort": [
                            {
                                "date": {
                                    "order": "desc"
                                }
                            }
                        ],
                        "_source": {
                            "includes": [ "date", "price" ]
                        },
                        "size" : 1
                    }
                }
            }
        }
    }
}

响应可能是:

{
  ...
  "aggregations": {
    "top_tags": {
       "doc_count_error_upper_bound": 0,
       "sum_other_doc_count": 0,
       "buckets": [
          {
             "key": "hat",
             "doc_count": 3,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 3,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmauCQpcRyxw6ChK",
                         "_source": {
                            "date": "2015/03/01 00:00:00",
                            "price": 200
                         },
                         "sort": [
                            1425168000000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          },
          {
             "key": "t-shirt",
             "doc_count": 3,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 3,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmauCQpcRyxw6ChL",
                         "_source": {
                            "date": "2015/03/01 00:00:00",
                            "price": 175
                         },
                         "sort": [
                            1425168000000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          },
          {
             "key": "bag",
             "doc_count": 1,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 1,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmatCQpcRyxw6ChH",
                         "_source": {
                            "date": "2015/01/01 00:00:00",
                            "price": 150
                         },
                         "sort": [
                            1420070400000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          }
       ]
    }
  }
}

字段折叠示例

字段折叠或结果分组是一项功能,它将结果集逻辑地分组,每个组返回前几个文档。 组的排序由组中第一个文档的相关性决定。 在 Elasticsearch 中,这可以通过将一个top_hits聚合器包裹为子聚合器的桶聚合器来实现。

在下面的例子中,我们搜索抓取的网页。 对于每个网页,我们存储网页的 body 及其所属的域(domain)。 通过在 domain 字段上定义一个 terms 聚合器,我们按照域对网页的结果集进行分组。 然后将 top_hits 聚合器定义为子聚合器,这样就可以收集每个桶的匹配命中的前几个。

此外,还定义了一个 max 聚合器,terms聚合器的排序特性使用该聚合器按照桶中最相关文档的相关性顺序返回桶。

POST /sales/_search
{
  "query": {
    "match": {
      "body": "elections"
    }
  },
  "aggs": {
    "top_sites": {
      "terms": {
        "field": "domain",
        "order": {
          "top_hit": "desc"
        }
      },
      "aggs": {
        "top_tags_hits": {
          "top_hits": {}
        },
        "top_hit" : {
          "max": {
            "script": {
              "source": "_score"
            }
          }
        }
      }
    }
  }
}

目前,需要max(或min)聚合器来确保来自terms聚合器的桶根据每个域的最相关网页的分数排序。 不幸的是,top_hits聚合器还不能用于terms 聚合器的order选项。

嵌套或反向嵌套聚合器中对 top_hits 的支持

如果 top_hits 聚合器包裹在嵌套(nested)反嵌套(reverse_nested)聚合器中,则返回嵌套的命中。 嵌套的命中在某种意义上是隐藏的迷你文档,它们是常规文档的一部分,其中在映射中已经配置了嵌套字段类型。 如果 top_hits 聚合器被包装在nestedreverse_nested聚合器中,那么它能够取消隐藏这些文档。 了解有关嵌套类型映射中嵌套的更多信息。

如果已经配置了嵌套类型,那么单个文档实际上被索引为多个 Lucene 文档,并且它们共享相同的id。 为了确定嵌套命中的身份,需要的不仅仅是id,这就是为什么嵌套命中还包括它们的嵌套身份。 嵌套标识保存在搜索命中的_nested字段下,并且包括嵌套命中所属的数组字段和数组字段中的偏移量。 偏移量从零开始。

让我们用一个真实的例子来看看它是如何工作的。比如下面这个映射:

PUT /sales
{
    "mappings": {
        "properties" : {
            "tags" : { "type" : "keyword" },
            "comments" : { 
                "type" : "nested",
                "properties" : {
                    "username" : { "type" : "keyword" },
                    "comment" : { "type" : "text" }
                }
            }
        }
    }
}

comments 是一个数组,它保存 product 对象下的嵌套文档。

并添加几个文档:

PUT /sales/_doc/1?refresh
{
    "tags": ["car", "auto"],
    "comments": [
        {"username": "baddriver007", "comment": "This car could have better brakes"},
        {"username": "dr_who", "comment": "Where's the autopilot? Can't find it"},
        {"username": "ilovemotorbikes", "comment": "This car has two extra wheels"}
    ]
}

现在可以执行下面的 top_hits 聚合(包裹在一个 nested 聚合中):

POST /sales/_search
{
    "query": {
        "term": { "tags": "car" }
    },
    "aggs": {
        "by_sale": {
            "nested" : {
                "path" : "comments"
            },
            "aggs": {
                "by_user": {
                    "terms": {
                        "field": "comments.username",
                        "size": 1
                    },
                    "aggs": {
                        "by_nested": {
                            "top_hits":{}
                        }
                    }
                }
            }
        }
    }
}

具有嵌套命中的前几个命中响应片段,位于数组字段 comments 的第一个槽中:

{
  ...
  "aggregations": {
    "by_sale": {
      "by_user": {
        "buckets": [
          {
            "key": "baddriver007",
            "doc_count": 1,
            "by_nested": {
              "hits": {
                "total" : {
                   "value": 1,
                   "relation": "eq"
                },
                "max_score": 0.3616575,
                "hits": [
                  {
                    "_index": "sales",
                    "_type" : "_doc",
                    "_id": "1",
                    "_nested": {
                      "field": "comments",  
                      "offset": 0 
                    },
                    "_score": 0.3616575,
                    "_source": {
                      "comment": "This car could have better brakes", 
                      "username": "baddriver007"
                    }
                  }
                ]
              }
            }
          }
          ...
        ]
      }
    }
  }
}

包含嵌套命中的数组字段的名称

嵌套命中包含数组时的位置

嵌套命中的源

如果请求_source,则只返回嵌套对象的部分源,而不是文档的整个源。 nested 内部对象级别上的存储字段也可以通过驻留在nestedreverse_nested聚合器中的top_hits聚合器来访问。

只有嵌套命中才会在命中中有 _nested 字段,非嵌套(常规)命中不会有_nested字段。

如果没有启用_source,那么_nested中的信息也可以用来解析其他地方的原始源。

如果在映射中定义了多级嵌套对象类型,那么_nested信息也可以是分层的,以便表达两层或更多层的嵌套命中的身份。

在下面的示例中,嵌套命中驻留在字段 nested_grand_child_field 的第一个槽中,然后驻留在nested_child_field字段的第二个槽中:

...
"hits": {
 "total" : {
     "value": 2565,
     "relation": "eq"
 },
 "max_score": 1,
 "hits": [
   {
     "_index": "a",
     "_type": "b",
     "_id": "1",
     "_score": 1,
     "_nested" : {
       "field" : "nested_child_field",
       "offset" : 1,
       "_nested" : {
         "field" : "nested_grand_child_field",
         "offset" : 0
       }
     }
     "_source": ...
   },
   ...
 ]
}
...