Elasticsearch聚合查询(指标聚合和桶聚合)
我们一直是在给定的文档语料库中搜索文档。搜索还可以用于分析。分析使组织能够从宏观层面全面地分析数据,获取对数据的洞察力,从而得出关于数据的结论。
在 Elasticsearch 中,我们使用聚合 API 来提供分析功能。聚合分为 3 类:
我将在本节中介绍几个指标聚合和桶聚合的例子。
与搜索类似,我们也使用 _search 端点来进行聚合操作。不过,我们要在请求中使用一个名为 aggs(aggregations 的缩写)的新对象来代替我们到目前为止所使用的 query 对象。为了展示 Elasticsearch 的真正威力,我们可以将搜索和聚合组合在单个查询中。
为了有效地展示聚合,我们需要索引一组新的数据,10 个国家的新型冠状病毒感染(COVID-19)相关数据。我们使用 _bulk API 索引的数据文件 covid-26march2021.txt 的片段如下列代码所示。
在学习指标聚合的实际应用之前,我们先来快速了解一下聚合的语法。聚合查询使用与搜索查询相同的 Query DSL 语法编写,下图展示了一个例子:

图 1 查找平均评分聚合的Query DSL语法
值得注意的是,aggs 是根层级对象,在 amazon_rating 字段上定义了一个 avg(平均值)指标。一旦执行了这个查询,它就会向用户返回聚合结果。让我们马上执行一些指标聚合查询。
通过这段代码,我们创建了一个聚合类型的查询:aggs 告诉 Elasticsearch 这是一个聚合任务。sum 指标是我们打算进行的聚合类型。这里我们要求引擎通过将每个国家的危重症患者数相加来得到危重症患者总数。响应类似于下面这样:
这个查询返回了数据集中 10 个国家的最高死亡病例数:
同样,我们也可以找到最小值(min)、平均值(avg)和其他指标。
例如,我们可以按照所列方式将数据划分到桶中:按年龄(20~30岁、31~40岁、41~50岁)对成年人进行分组,按评分对电影进行分组,或按每月新建房屋的数量进行分组。
Elasticsearch 提供了 20 多种开箱即用的聚合,每种都有自己的分桶策略。更重要的是,可以在主桶下嵌套聚合。让我们来看几个桶聚合的实际应用。
例如,如果想按危重症患者数每间隔 2 500 作为一个区间(桶)对国家进行分组,可以编写如下代码所示的查询。
响应类似于以下内容,其中每个桶都有一个键和一个值:
在 Elasticsearch 中,我们使用聚合 API 来提供分析功能。聚合分为 3 类:
- 指标聚合(metric aggregation):如 sum、min、max 和 avg 之类的简单聚合。它们提供了一组文档数据的聚合值。
- 桶聚合(bucket aggregation):按天数、年龄组等间隔进行分类,将数据收集到“桶”中的聚合。这些数据有助于构建直方图、饼图和其他可视化图表。
- 管道聚合(pipeline aggregation):对其他聚合的输出结果进行处理的聚合。
我将在本节中介绍几个指标聚合和桶聚合的例子。
与搜索类似,我们也使用 _search 端点来进行聚合操作。不过,我们要在请求中使用一个名为 aggs(aggregations 的缩写)的新对象来代替我们到目前为止所使用的 query 对象。为了展示 Elasticsearch 的真正威力,我们可以将搜索和聚合组合在单个查询中。
为了有效地展示聚合,我们需要索引一组新的数据,10 个国家的新型冠状病毒感染(COVID-19)相关数据。我们使用 _bulk API 索引的数据文件 covid-26march2021.txt 的片段如下列代码所示。
POST covid/_bulk {"index":{}} {"country":"USA","date":"2021-03-26","deaths":561142,"recovered":23275268} {"index":{}} {"country":"Brazil","date":"2021-03-26","deaths":307326,"recovered":10824095} ..._bulk API 将 10 个文档索引到新创建的 covid 索引中。由于我们不关心文档 ID,因此我们让系统为每个文档生成一个随机 ID,因而在 API 的索引操作中,索引名称和 ID 是空的:{"index":{}}。现在在 covid 索引中有了一组文档,让我们从指标聚合开始,完成一些基本的聚合任务。
指标聚合
指标聚合是我们在日常生活中经常使用的简单聚合,例如,一个班级中学生的平均身高是多少,最低的对冲交易额是多少,一部电影的票房是多少。Elasticsearch 提供了许多这样的指标,其中大多数都很直观。在学习指标聚合的实际应用之前,我们先来快速了解一下聚合的语法。聚合查询使用与搜索查询相同的 Query DSL 语法编写,下图展示了一个例子:

图 1 查找平均评分聚合的Query DSL语法
值得注意的是,aggs 是根层级对象,在 amazon_rating 字段上定义了一个 avg(平均值)指标。一旦执行了这个查询,它就会向用户返回聚合结果。让我们马上执行一些指标聚合查询。
1) 查找危重症患者总数(sum指标)
回到我们的 COVID-19 数据,假设我们想要查找所有 10 个国家的危重症患者总数。为此,我们可以使用 sum 指标,代码如下:GET covid/_search { "aggs": { ←--- 使用aggs对象的聚合查询 "critical_patients": { ←--- 用户自定义的聚合请求名称 "sum": { ←--- sum指标将危重症患者数相加 "field": "critical" ←--- 应用聚合的字段 } } } }
通过这段代码,我们创建了一个聚合类型的查询:aggs 告诉 Elasticsearch 这是一个聚合任务。sum 指标是我们打算进行的聚合类型。这里我们要求引擎通过将每个国家的危重症患者数相加来得到危重症患者总数。响应类似于下面这样:
"aggregations" : { "critical_patients" : { "value" : 44045.0 } }在响应中,危重症患者总数以我们指定的名称(critical_patients)返回。注意,如果我们没有明确要求抑制返回的文档,那么响应将包含所有返回的文档。我们可以在根层级设置 "size": 0,以阻止响应包含原始文档:
"size": 0,现在我们知道了 sum 指标是如何进行聚合的,让我们再看看其他几个指标。
2) 使用其他指标聚合
类似地,如果我们想要找出 COVID-19 数据中所有国家的最高死亡病例数,就可以使用 max 指标,代码如下:GET covid/_search { "size": 0, "aggs": { "max_deaths": { "max": { "field": "deaths" } } } }
这个查询返回了数据集中 10 个国家的最高死亡病例数:
"aggregations" : { "max_deaths" : { "value" : 561142.0 } }
同样,我们也可以找到最小值(min)、平均值(avg)和其他指标。
桶聚合
桶聚合,或简称为分桶(bucketing),将数据划分到不同的组或者桶(bucket)中。例如,我们可以按照所列方式将数据划分到桶中:按年龄(20~30岁、31~40岁、41~50岁)对成年人进行分组,按评分对电影进行分组,或按每月新建房屋的数量进行分组。
Elasticsearch 提供了 20 多种开箱即用的聚合,每种都有自己的分桶策略。更重要的是,可以在主桶下嵌套聚合。让我们来看几个桶聚合的实际应用。
1) 直方图
histogram 桶聚合会遍历所有文档,根据数值大小将它们划分到不同的桶中。例如,如果想按危重症患者数每间隔 2 500 作为一个区间(桶)对国家进行分组,可以编写如下代码所示的查询。
GET covid/_search { "size": 0, "aggs": { "critical_patients_as_histogram": { ←--- 用户自定义的聚合请求名称 "histogram": { ←--- 桶聚合的类型:在本例中是直方图 "field": "critical", ←--- 应用聚合的字段 "interval": 2500 ←--- 桶的间隔 } } } }
响应类似于以下内容,其中每个桶都有一个键和一个值:
"aggregations" : { "critical_patients_as_histogram" : { "buckets" : [{ "key" : 0.0, "doc_count" : 4 }, { "key" : 2500.0, "doc_count" : 3 }, { "key" : 5000.0, "doc_count" : 0 }, { "key" : 7500.0, "doc_count" : 3 }] } }第一个桶有 4 个文档(国家),每个国家的危重症患者数在 2 500 以内。第二个桶有 3 个国家,每个国家的危重症患者数在 2 500 到 5 000 之间,以此类推。
2) 范围桶
range 桶聚合根据预定义的范围定义一组桶。例如,假设我们想使用 range 桶聚合在自定义范围(60 000以内,60 000~70 000,70 000~80 000,以及80 000~120 000)内按国家来划分 COVID-19 死亡病例数。我们可以如下代码所示定义范围。GET covid/_search { "size": 0, "aggs": { "range_countries": { "range": { ←--- range桶聚合 "field": "deaths", ←--- 应用聚合的字段 "ranges": [ ←--- 定义自定义范围的数组 {"to": 60000}, {"from": 60000,"to": 70000}, {"from": 70000,"to": 80000}, {"from": 80000,"to": 120000} ] } } } }我们定义了一个 range 聚合桶类型,并设置了一组自定义范围。执行查询后,结果中的桶就会以自定义范围作为键,展示出每个范围对应的文档数量:
"doc_count" : 1"doc_count" : 0"doc_count" : 2"doc_count" : 3结果表明,有一个国家的死亡病例数在 60 000 以内,有 3 个国家的死亡病例数在 80 000 到 120 000 之间,以此类推。