掌握 ElasticSearch 聚合查询:Aggregations 入门与实战
掌握 ElasticSearch 聚合查询:Aggregations 入门与实战
- 一、引言 (Introduction)
- 二、数据准备 (Data Preparation)
- 2.1 创建索引 (Create Index)
- 2.2 批量导入数据 (Bulk Import Data)
- 三、聚合查询基础 (Aggregation Basics)
- 3.1 什么是聚合查询?(What are Aggregations?)
- 3.2 聚合查询的基本结构 (Basic Aggregation Structure)
- 3.3 聚合类型 (Aggregation Types)
- 四、指标聚合 (Metrics Aggregations)
- 4.1 `avg` (平均值)
- 4.2 `min` (最小值)
- 4.3 `max` (最大值)
- 4.4 `sum` (总和)
- 4.5 `stats` (统计信息)
- 4.6 `value_count` (值计数)
- 4.7 `cardinality` (基数/去重计数)
- 五、桶聚合 (Bucket Aggregations)
- 5.1 `terms` (词条聚合)
- 5.2 嵌套桶聚合
- 六、管道聚合 (Pipeline Aggregations)
- 6.1 什么是管道聚合?
- 6.2 `min_bucket` (最小桶)
- 6.3 其他管道聚合
- 七、实战案例 (Practical Examples)
- 案例 1:统计每个产品类别中,价格最高的产品的价格,并按最高价格降序排列
- 案例 2:找出每个月销售额最高的日期
- 八、总结 (Conclusion)
一、引言 (Introduction)
在信息检索和数据分析的世界中,我们常常需要做的不仅仅是找到匹配特定关键词的文档。很多时候,我们需要从海量数据中提取出更深层次的、有价值的洞察。例如:
- 我想知道我的电商网站上所有商品的平均价格是多少?
- 哪个产品类别下的商品数量最多?
- 我的网站上有多少种不同的商品品牌?
- 在过去的一年中,每个月的销售总额是多少?
- 哪一天的销售额是最高的?
这些问题都涉及对数据的统计和分析,而不仅仅是简单的搜索。为了满足这些需求,Elasticsearch 提供了强大的聚合查询(Aggregations) 功能。聚合查询就像一个多功能的瑞士军刀,或者说是一个强大的数据分析工具箱,它允许你对数据进行各种分组、统计和计算,从而提取出隐藏在数据背后的关键信息。
你可以把聚合查询想象成 SQL 中的 GROUP BY
子句和各种聚合函数(COUNT
, SUM
, AVG
, MIN
, MAX
)的组合,但 Elasticsearch 的聚合功能远比 SQL 更加灵活和强大。
本文将带你深入了解 Elasticsearch 7.10 版本中聚合查询的基础知识。通过本文,你将学习到:
- 聚合查询的基本概念和工作原理。
- 三种核心的聚合类型:指标聚合(Metrics Aggregations)、桶聚合(Bucket Aggregations)和管道聚合(Pipeline Aggregations)。
- 如何使用各种指标聚合函数(
avg
,min
,max
,sum
,stats
,value_count
,cardinality
)。 - 如何使用
terms
聚合进行分组。 - 如何使用管道聚合对聚合结果进行二次聚合。
- 通过实战案例学习如何在实际应用中运用聚合查询,解决真实的数据分析问题。
二、数据准备 (Data Preparation)
在开始学习聚合查询之前,我们需要先准备一些示例数据。我们将创建一个名为 product
的索引,并批量导入一些商品数据。
2.1 创建索引 (Create Index)
首先,我们创建一个名为 product
的索引,并定义其 mappings(映射)。Mappings 定义了索引中每个字段的数据类型以及如何进行索引和搜索。
PUT product
{
"mappings": {
"properties": {
"createtime": {
"type": "date"
},
"date": {
"type": "date"
},
"desc": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"lv": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {
"type": "long"
},
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
2.2 批量导入数据 (Bulk Import Data)
接下来,我们使用 Elasticsearch 的 _bulk
API 来批量导入一些商品数据。_bulk
API 可以一次性执行多个索引、更新或删除操作,效率更高。
POST /product/_bulk
{"index":{"_index": "product"}}
{"name": "小米手机", "desc": "手机中的战斗机", "price": 3999, "lv": "旗舰机", "type": "手机", "createtime": "2020-10-01T08:00:00Z", "tags": [ "性价比", "发烧", "不卡顿" ]}
{"index":{"_index": "product"}}
{"name": "小米NFC手机", "desc": "支持全功能NFC,手机中的滑翔机", "price": 4999, "lv": "旗舰机", "type": "手机", "createtime": "2020-05-21T08:00:00Z", "tags": [ "性价比", "发烧", "公交卡" ]}
{"index":{"_index": "product"}}
{"name": "NFC手机", "desc": "手机中的轰炸机", "price": 2999, "lv": "高端机", "type": "手机", "createtime": "2020-06-20T08:00:00Z", "tags": [ "性价比", "快充", "门禁卡" ]}
{"index":{"_index": "product"}}
{"name": "小米耳机", "desc": "耳机中的黄焖鸡", "price": 999, "lv": "百元机", "type": "耳机", "createtime": "2020-06-23T08:00:00Z", "tags": [ "降噪", "防水", "蓝牙" ]}
{"index":{"_index": "product"}}
{"name": "红米耳机", "desc": "耳机中的肯德基", "price": 399, "type": "耳机", "lv": "百元机", "createtime": "2020-07-20T08:00:00Z", "tags": [ "防火", "低音炮", "听声辨位" ]}
{"index":{"_index": "product"}}
{"name": "小米手机10", "desc": "充电贼快掉电更快,超级无敌望远镜,高刷电竞屏", "price": null, "lv": "旗舰机", "type": "手机", "createtime": "2020-07-27T08:00:00Z", "tags": [ "120HZ刷新率", "120W快充", "120倍变焦" ]}
{"index":{"_index": "product"}}
{"name": "挨炮 SE2", "desc": "除了CPU,一无是处", "price": 3299, "lv": "旗舰机", "type": "手机", "createtime": "2020-07-21T08:00:00Z", "tags": [ "割韭菜", "割韭菜", "割新韭菜" ]}
{"index":{"_index": "product"}}
{"name": "XS Max", "desc": "听说要出新款12手机了,终于可以换掉手中的4S了", "price": 4399, "lv": "旗舰机", "type": "手机", "createtime": "2020-08-19T08:00:00Z", "tags": [ "5V1A", "4G全网通", "大" ]}
{"index":{"_index": "product"}}
{"name": "小米电视", "desc": "70寸性价比只选,不要一万八,要不要八千八,只要两千九百九十八", "price": 2998, "lv": "高端机", "type": "电视", "createtime": "2020-08-16T08:00:00Z", "tags": [ "巨馍", "家庭影院", "游戏" ]}
{"index":{"_index": "product"}}
{"name": "红米电视", "desc": "我比上边那个更划算,我也2998,我也70寸,但是我更好看", "price": 2999, "type": "电视", "lv": "高端机", "createtime": "2020-08-28T08:00:00Z", "tags": [ "大片", "蓝光8K", "超薄" ]}
{"index":{"_index": "product"}}
{"name": "红米电视", "desc": "我比上边那个更划算,我也2998,我也70寸,但是我更好看", "price": 2998, "type": "电视", "lv": "高端机", "createtime": "2020-08-28T08:00:00Z", "tags": [ "大片", "蓝光8K", "超薄" ]}
代码解释:
POST /product/_bulk
: 使用_bulk
API 向product
索引发送批量请求。- 每一行是一个 JSON 对象,表示一个操作。
现在,我们已经准备好了数据,可以开始学习 Elasticsearch 的聚合查询了!
三、聚合查询基础 (Aggregation Basics)
3.1 什么是聚合查询?(What are Aggregations?)
聚合查询(Aggregations)是 Elasticsearch 中一种强大的数据分析功能,它允许你对文档数据进行各种统计分析。与搜索查询(返回匹配的文档)不同,聚合查询返回的是聚合后的统计结果。
你可以将聚合查询类比为 SQL 中的 GROUP BY
子句和聚合函数(如 COUNT
, SUM
, AVG
, MIN
, MAX
)。 例如,你可以使用 SQL 来计算每个部门的平均工资:
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;
Elasticsearch 的聚合查询提供了类似的功能,但更加灵活和强大。它可以处理更复杂的数据结构和分析场景,并且可以进行多层嵌套的聚合。
3.2 聚合查询的基本结构 (Basic Aggregation Structure)
聚合查询的基本语法结构如下:
GET /<index>/_search
{
"size": 0,
"aggs": {
"<aggregation_name>": {
"<aggregation_type>": {
"<aggregation_parameters>"
},
"aggs": {
"<nested_aggregation_name>": {
"<nested_aggregation_type>": {
"<nested_aggregation_parameters>"
}
}
}
}
}
}
解释:
GET /<index>/_search
: 这是 Elasticsearch 的搜索 API,我们在这里使用它来执行聚合查询。"size": 0
: 这是一个可选参数。通常,在执行聚合查询时,我们只关心聚合结果,而不关心具体的文档内容。"size": 0
表示不返回任何文档,只返回聚合结果。"aggs"
(或"aggregations"
): 这是聚合查询的顶层关键字。所有聚合操作都定义在aggs
对象中。"<aggregation_name>"
: 这是你为聚合操作指定的名称。这个名称可以是任意的,它将作为聚合结果的一部分返回,方便你识别和引用。例如,你可以将计算平均价格的聚合命名为"avg_price"
。"<aggregation_type>"
: 这是聚合的类型。Elasticsearch 提供了多种聚合类型,每种类型都有不同的功能。常见的聚合类型包括:avg
(平均值)min
(最小值)max
(最大值)sum
(总和)terms
(词条聚合)stats
(统计信息)- 等等…
"<aggregation_parameters>"
: 这是特定于聚合类型的参数。不同的聚合类型有不同的参数。例如,avg
聚合需要指定要计算平均值的字段,terms
聚合需要指定要分组的字段。"aggs": { ... }
(在<aggregation_name>
内部): 这是可选的嵌套聚合。你可以在一个聚合操作中嵌套另一个聚合操作,以实现更复杂的分析。例如,你可以先按产品类别分组,然后在每个类别中计算平均价格。
3.3 聚合类型 (Aggregation Types)
Elasticsearch 提供了三种主要的聚合类型:
-
Metrics Aggregations (指标聚合): 这类聚合主要用于计算单个数值指标。例如:
avg
: 计算平均值。min
: 计算最小值。max
: 计算最大值。sum
: 计算总和。stats
: 一次性计算多个统计值(avg
,min
,max
,sum
,count
)。value_count
: 计算非空字段的文档数量。cardinality
: 计算字段的不同值的数量(去重)。
-
Bucket Aggregations (桶聚合): 这类聚合主要用于将文档分组到不同的“桶”中。每个桶代表一个分组。例如:
terms
: 按字段值对文档进行分组。date_histogram
: 按日期范围对文档进行分组。range
: 按自定义数值范围对文档进行分组。filter
: 根据指定的过滤条件将文档分到一个桶filters
: 根据指定的多个过滤条件将文档分到多个桶
-
Pipeline Aggregations (管道聚合): 这类聚合比较特殊,它们不直接操作文档,而是对其他聚合的结果进行进一步的聚合。例如:
min_bucket
: 找出包含最小值的桶。max_bucket
: 找出包含最大值的桶。avg_bucket
: 计算桶的平均值。sum_bucket
: 计算桶的总和。stats_bucket
: 对桶进行统计分析derivative
: 计算导数(例如,计算销售额的变化率)。
本文将重点介绍这三种聚合类型的基础用法。接下来,我们将分别深入探讨每种聚合类型,并通过示例演示如何在实际应用中使用它们。
四、指标聚合 (Metrics Aggregations)
指标聚合用于计算单个数值指标,例如平均值、最小值、最大值、总和等。这些指标可以帮助你了解数据的整体特征。
4.1 avg
(平均值)
avg
聚合用于计算指定字段的平均值。
示例: 计算 product
索引中所有产品的平均价格。
GET /product/_search
{
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
代码解释:
"size": 0
: 不返回文档,只返回聚合结果。"aggs"
: 聚合查询的开始。"avg_price"
: 我们为这个聚合操作指定的名称。"avg"
: 指定聚合类型为avg
(平均值)。"field": "price"
: 指定要计算平均值的字段为price
。
结果 (部分):
{
...
"aggregations": {
"avg_price": {
"value": 3008.8
}
}
}
结果中的 "value"
字段显示了所有产品价格的平均值。
4.2 min
(最小值)
min
聚合用于计算指定字段的最小值。
示例: 计算 product
索引中所有产品的最低价格。
GET /product/_search
{
"size": 0,
"aggs": {
"min_price": {
"min": {
"field": "price"
}
}
}
}
结果 (部分):
{
...
"aggregations": {
"min_price": {
"value": 399.0
}
}
}
4.3 max
(最大值)
max
聚合用于计算指定字段的最大值。
示例: 计算 product
索引中所有产品的最高价格。
GET /product/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
结果 (部分):
{
...
"aggregations": {
"max_price": {
"value": 4999.0
}
}
}
4.4 sum
(总和)
sum
聚合用于计算指定字段的总和。
示例: 计算 product
索引中所有产品的价格总和。
GET /product/_search
{
"size": 0,
"aggs": {
"sum_price": {
"sum": {
"field": "price"
}
}
}
}
结果 (部分):
{
...
"aggregations": {
"sum_price": {
"value": 30088.0
}
}
}
4.5 stats
(统计信息)
stats
聚合可以一次性计算多个统计值,包括:
count
: 文档数量。min
: 最小值。max
: 最大值。avg
: 平均值。sum
: 总和。
示例: 获取 product
索引中所有产品的价格统计信息。
GET /product/_search
{
"size": 0,
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
结果 (部分):
{
...
"aggregations" : {
"statistics" : {
"count" : 10,
"min" : 399.0,
"max" : 4999.0,
"avg" : 3008.8,
"sum" : 30088.0
}
}
}
结果中一次性返回了count
,min
,max
,avg
和sum
4.6 value_count
(值计数)
value_count
聚合用于计算指定字段的 非空 值的文档数量。
示例: 计算 product
索引中有多少个文档具有 price
字段(即有多少个产品有价格信息)。
GET /product/_search
{
"size": 0,
"aggs": {
"price_count": {
"value_count": {
"field": "price"
}
}
}
}
结果 (部分):
{
...
"aggregations": {
"price_count": {
"value": 10
}
}
}
注意,由于有一个文档的 price
字段为 null
,因此结果为 10,而不是 11。
4.7 cardinality
(基数/去重计数)
cardinality
聚合用于计算指定字段的不同值的数量(即去重计数)。
示例: 计算 product
索引中有多少种不同的产品等级 (lv
)。
GET /product/_search
{
"size": 0,
"aggs": {
"lv_cardinality": {
"cardinality": {
"field": "lv.keyword"
}
}
}
}
代码解释:
"lv_cardinality"
: 聚合的名称。"cardinality"
: 指定聚合类型为cardinality
。"field": "lv.keyword"
: 指定要计算基数的字段为lv.keyword
。 这里使用.keyword
子字段是因为我们要对原始的、未分词的lv
值进行去重计数。
结果 (部分):
{
...
"aggregations": {
"lv_cardinality": {
"value": 3
}
}
}
结果表明,product
索引中有 3 种不同的产品等级。
cardinality
聚合的计算结果是近似的,而不是完全精确的。对于低基数字段(即不同值较少),结果通常是准确的。但对于高基数字段(即不同值非常多),为了提高性能,Elasticsearch 使用了一种称为 HyperLogLog++ 的算法进行近似计算。你可以通过precision_threshold
参数来控制精度和内存使用之间的权衡。如果需要完全精确的去重计数,并且数据集较小,可以考虑使用terms
聚合,并设置一个足够大的size
值。但对于大数据集,cardinality
聚合通常是更好的选择。
五、桶聚合 (Bucket Aggregations)
桶聚合(Bucket Aggregations)用于将文档分组到不同的“桶”中。每个桶代表一个分组,可以根据不同的条件来创建桶。桶聚合本身不进行统计运算,其主要作用是分组。通常会在桶聚合内嵌套一个或者多个指标聚合,用于统计每个桶内的指标。
5.1 terms
(词条聚合)
terms
聚合是最常用的桶聚合之一。它根据指定字段的值对文档进行分组,每个不同的字段值都会创建一个桶。
示例: 按 tags.keyword
字段对 product
索引中的产品进行分组,并统计每个标签下的文档数量。
GET /product/_search
{
"size": 0,
"aggs": {
"tag_bucket": {
"terms": {
"field": "tags.keyword",
"size": 10,
"order": {
"_count": "desc"
}
}
}
}
}
代码解释:
"size": 0
: 不返回文档,只返回聚合结果。"aggs"
: 聚合查询的开始。"tag_bucket"
: 我们为这个聚合操作指定的名称。"terms"
: 指定聚合类型为terms
(词条聚合)。"field": "tags.keyword"
: 指定要进行分组的字段为tags.keyword
。使用.keyword
子字段是因为我们要基于标签的原始值进行分组,而不是分词后的结果。"size": 10
: 指定返回的桶的最大数量。默认情况下,terms
聚合会返回文档数量最多的前 10 个桶。"order": { "_count": "desc" }
: 指定桶的排序方式。这里按照每个桶中文档数量 (_count
) 的降序 (desc
) 进行排序。
结果(部分):
{
...
"aggregations": {
"tag_bucket": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 8,
"buckets": [
{
"key": "性价比",
"doc_count": 3
},
{
"key": "发烧",
"doc_count": 2
},
{
"key": "大片",
"doc_count": 2
},
{
"key": "蓝光8K",
"doc_count": 2
},
{
"key": "超薄",
"doc_count": 2
},
{
"key": "割韭菜",
"doc_count": 2
},
{
"key": "120W快充",
"doc_count": 1
},
{
"key": "120HZ刷新率",
"doc_count": 1
},
{
"key": "120倍变焦",
"doc_count": 1
},
{
"key": "4G全网通",
"doc_count": 1
}
]
}
}
}
结果解释:
"buckets"
: 这是一个数组,包含了根据tags.keyword
字段值分组后的桶。"key"
: 每个桶的键,即tags.keyword
字段的值(例如 “性价比”, “发烧”)。"doc_count"
: 每个桶中的文档数量。"doc_count_error_upper_bound"
:由于terms
聚合默认情况下返回文档数量最多的前N个桶,这个值表示因为桶数量限制,没有被统计到的文档数量的最大可能误差值。"sum_other_doc_count"
: 由于terms
聚合默认情况下返回文档数量最多的前N个桶,这个值表示未返回的其他桶中文档数量的总和。
size
参数:
size
参数控制返回的桶的数量。 如果你想返回所有桶,可以将 size
设置为一个较大的值(例如,大于字段中不同值的数量)。 但是,请注意,如果字段的基数非常高(即有很多不同的值),返回所有桶可能会消耗大量内存。
order
参数:
order
参数控制桶的排序方式。除了按 _count
排序外,还可以:
- 按
_key
排序:"order": { "_key": "asc" }
(按字段值升序) 或"order": { "_key": "desc" }
(按字段值降序)。 - 按桶内指标聚合的结果排序(稍后在嵌套聚合中介绍)。
5.2 嵌套桶聚合
在实际应用中,我们经常需要进行多层级的聚合。例如,我们想先按type
进行分组,然后统计每个type
中price
的平均值。这种情况下我们就需要用到嵌套聚合。
GET /product/_search
{
"size": 0,
"aggs": {
"type_bucket": {
"terms": {
"field": "type.keyword"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
代码解释:
- 外层是一个
terms
聚合, 根据type.keyword
字段进行分组。 - 内层是一个
avg
聚合, 计算每个分组内price
字段的平均值。 - 内层聚合的结果会作为外层聚合每个桶的一个属性。
结果(部分):
{
...
"aggregations": {
"type_bucket": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key" : "手机",
"doc_count" : 6,
"avg_price" : {
"value" : 3939.0
}
},
{
"key" : "耳机",
"doc_count" : 3,
"avg_price" : {
"value" : 1465.3333333333333
}
},
{
"key" : "电视",
"doc_count" : 2,
"avg_price" : {
"value" : 2998.5
}
}
]
}
}
}
结果解释:
"buckets"
: 这是一个数组,包含了根据type.keyword
字段值分组后的桶。"key"
: 每个桶的键,即type.keyword
字段的值(例如 “手机”, “耳机”)。"doc_count"
: 每个桶中的文档数量。"avg_price"
: 每个桶中嵌套聚合的结果, 即该类型商品的平均价格。
通过嵌套聚合,我们可以轻松实现多层级的数据分析。我们可以根据需求自由组合不同的聚合类型,构建出非常复杂的聚合查询。
六、管道聚合 (Pipeline Aggregations)
管道聚合(Pipeline Aggregations)是一种特殊的聚合类型。它们不像指标聚合和桶聚合那样直接操作文档,而是对其他聚合的结果进行进一步的聚合。这就像在数据处理流程中添加了一个额外的“管道”,对上游聚合的输出进行处理。
6.1 什么是管道聚合?
管道聚合的核心思想是:输入是另一个聚合(或多个聚合)的输出,而不是文档本身。这使得我们可以进行诸如以下操作:
- 找出平均价格最低的产品类别。
- 计算每个月销售额的总和,然后找出销售额最高的月份。
- 计算某个指标的导数或移动平均值。
管道聚合的关键参数是 buckets_path
,它用于指定要作为输入的聚合的路径。
6.2 min_bucket
(最小桶)
min_bucket
管道聚合用于找出包含最小值的桶。
示例: 找出平均价格最低的产品分类(基于之前嵌套聚合的结果)。
GET /product/_search
{
"size": 0,
"aggs": {
"type_bucket": {
"terms": {
"field": "type.keyword"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
},
"min_avg_price_bucket": {
"min_bucket": {
"buckets_path": "type_bucket>avg_price"
}
}
}
}
代码解释:
"type_bucket"
: 这是一个terms
聚合,按产品类型 (type.keyword
) 分组。"avg_price"
: 这是一个嵌套的avg
聚合,计算每个产品类型的平均价格。"min_avg_price_bucket"
: 这是我们定义的管道聚合的名称。"min_bucket"
: 指定聚合类型为min_bucket
。"buckets_path": "type_bucket>avg_price"
: 这是buckets_path
参数,它指定了要处理的聚合路径。type_bucket
: 表示外层的terms
聚合。>
: 表示嵌套关系。avg_price
: 表示内层的avg
聚合。- 所以,
"type_bucket>avg_price"
表示我们要找到type_bucket
聚合中,avg_price
聚合结果最小的那个桶。
结果(部分):
{
...
"aggregations": {
"type_bucket": {
... // 省略了 type_bucket 的详细结果
},
"min_avg_price_bucket": {
"value": 1465.3333333333333,
"keys": [
"耳机"
]
}
}
}
结果解释:
"min_avg_price_bucket"
: 管道聚合的结果。"value"
: 最小值 (最低的平均价格)。"keys"
: 取得最小值的桶的key
值数组, 在本例中, 平均价格最低的分类是 “耳机”。
6.3 其他管道聚合
除了 min_bucket
之外,ElasticSearch 还提供了其他几种管道聚合:
max_bucket
: 找出包含最大值的桶。用法与min_bucket
类似, 只是找出最大值。avg_bucket
: 计算所有桶中某个指标的平均值。sum_bucket
: 计算所有桶中某个指标的总和。stats_bucket
:一次性计算多个统计值, 类似于stats
指标聚合, 但是stats_bucket
是作用于桶聚合的结果之上。derivative
: 计算导数(例如,计算销售额的变化率)。moving_avg
: 计算移动平均值(例如,计算过去 7 天的平均销售额)。bucket_script
: 使用脚本对桶的指标进行自定义计算。bucket_selector
: 根据脚本过滤桶。bucket_sort
: 对桶进行排序。
这些管道聚合提供了更高级的数据分析功能。你可以在 ElasticSearch 的官方文档中找到关于它们的详细信息。
七、实战案例 (Practical Examples)
现在,让我们通过几个更贴近实际应用的示例,来展示如何组合不同类型的聚合,以解决真实的数据分析问题。
案例 1:统计每个产品类别中,价格最高的产品的价格,并按最高价格降序排列
这个案例结合了 terms
桶聚合、max
指标聚合和排序。
GET product/_search
{
"size": 0,
"aggs": {
"type_bucket": {
"terms": {
"field": "type.keyword",
"order": {
"max_price": "desc"
}
},
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
}
}
代码解释:
"type_buckets"
:terms
聚合,按type.keyword
字段(产品类型)分组。"order": { "max_price": "desc" }
: 按嵌套的max_price
聚合的结果(即每个类别中的最高价格)进行降序排序。"max_price"
: 嵌套的max
聚合,计算每个类别中的最高价格。
结果(部分):
{
...
"aggregations": {
"type_buckets": {
...
"buckets": [
{
"key" : "手机",
"doc_count" : 6,
"max_price" : {
"value" : 4999.0
}
},
{
"key" : "电视",
"doc_count" : 2,
"max_price" : {
"value" : 2999.0
}
},
{
"key" : "耳机",
"doc_count" : 3,
"max_price" : {
"value" : 2998.0
}
}
]
}
}
}
案例 2:找出每个月销售额最高的日期
这个案例结合了 date_histogram
桶聚合、sum
指标聚合和 max_bucket
管道聚合。
GET /product/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "createtime",
"calendar_interval": "month"
},
"aggs": {
"sales_per_day": {
"date_histogram": {
"field": "createtime",
"calendar_interval": "day"
},
"aggs": {
"daily_sales": {
"sum": {
"field": "price"
}
}
}
},
"max_daily_sales": {
"max_bucket": {
"buckets_path": "sales_per_day>daily_sales"
}
}
}
}
}
}
代码解释:
-
sales_per_month
(外层date_histogram
):- 按月对文档进行分组(
"calendar_interval": "month"
)。 field
: “createtime”
- 按月对文档进行分组(
-
sales_per_day
(内层date_histogram
):- 在每个月的桶内,再按天对文档进行分组(
"calendar_interval": "day"
)。 field
: “createtime”
- 在每个月的桶内,再按天对文档进行分组(
-
daily_sales
(指标聚合):- 在每个天的桶内,计算当天的销售总额(
"sum": { "field": "price" }
)。
- 在每个天的桶内,计算当天的销售总额(
-
max_daily_sales
(管道聚合):- 使用
max_bucket
管道聚合。 "buckets_path": "sales_per_day>daily_sales"
: 找出每个月内,daily_sales
(销售总额) 最高的那个sales_per_day
(天) 桶。
- 使用
结果 (部分):
{
...
"aggregations": {
"sales_per_month": {
"buckets": [
{
"key_as_string": "2020-05-01T00:00:00.000Z",
"key": 1588291200000,
"doc_count": 1,
"sales_per_day": {
...
},
"max_daily_sales": {
"value": 4999.0,
"keys": [
"2020-05-21T00:00:00.000Z"
]
}
},
{
"key_as_string": "2020-06-01T00:00:00.000Z",
"key": 1590969600000,
"doc_count": 2,
"sales_per_day": {
...
},
"max_daily_sales": {
"value": 2999.0,
"keys": [
"2020-06-20T00:00:00.000Z"
]
}
},
...
]
}
}
}
结果显示, 对于每个月, 销售额最高的那一天的日期和销售额都被找了出来. 例如, 在2020年5月, 销售额最高的那一天是2020-05-21, 销售额是4999.
这两个案例展示了如何将不同类型的聚合组合起来,以解决更复杂的数据分析问题。 ElasticSearch 聚合的强大之处在于其灵活性和可组合性,你可以根据自己的需求设计出各种各样的聚合查询。\
八、总结 (Conclusion)
在本教程中,我们深入探讨了 Elasticsearch 7.10 中聚合查询的基础知识。聚合查询是 Elasticsearch 中进行数据分析的强大工具,它能够帮助你从海量数据中提取出有价值的统计信息和洞察。
我们学习了:
- 聚合查询的基本概念和结构: 了解了聚合查询与搜索查询的区别,以及聚合查询的基本 JSON 结构。
- 三种核心的聚合类型:
- 指标聚合 (Metrics Aggregations): 用于计算单个数值指标,如平均值、最小值、最大值、总和、去重计数等。
- 桶聚合 (Bucket Aggregations): 用于将文档分组到不同的桶中,如按字段值分组、按日期范围分组等。
- 管道聚合 (Pipeline Aggregations): 用于对其他聚合的结果进行进一步的聚合,如找出最大值、最小值、计算导数等。
- 各种常用的聚合函数:
avg
,min
,max
,sum
,stats
,value_count
,cardinality
,terms
,min_bucket
,max_bucket
等。 - 如何使用嵌套聚合: 将不同类型的聚合组合起来,实现更复杂的数据分析。
- 实战案例: 通过两个实际案例,展示了如何运用聚合查询解决真实的数据分析问题。
Elasticsearch 的聚合功能远不止于此。本文只是一个入门指南,涵盖了最基础和最常用的部分。要充分发挥 Elasticsearch 聚合的威力,你需要不断学习和实践,探索更高级的聚合类型和用法。
进一步学习的建议:
- 阅读 Elasticsearch 官方文档: 官方文档是学习 ElasticSearch 的最佳资源。关于聚合的详细文档,请参考
- 尝试更多的聚合类型: 除了本文介绍的聚合类型外,ElasticSearch 还提供了许多其他聚合类型,如
date_histogram
、range
、filters
、geo_distance
、percentiles
、top_hits
等等。 - 练习、练习、再练习: 最好的学习方法就是实践。尝试使用不同的数据集和不同的聚合组合,来解决各种数据分析问题。
希望本文能帮助你入门 Elasticsearch 聚合查询。祝你在数据分析的道路上不断进步!