存储桶聚合:ElasticSearch数据分组基础
存储桶聚合基础概念
在 Elasticsearch 中,存储桶聚合(Bucket Aggregations)是一种强大的数据分组方式。它能够按照特定的条件对文档进行分组,将相似的文档划分到同一个 “桶” 中。就如同我们在日常生活中整理物品,将同一类别的物品放在同一个容器里。存储桶聚合在数据分析场景中极为重要,它能帮助我们从海量数据中提取有价值的信息,进行分类统计等操作。
常见的存储桶聚合类型
-
Terms 聚合
- 原理:Terms 聚合根据文档中某个字段的值进行分组。它会对该字段的每个唯一值创建一个桶,每个桶包含具有该特定值的所有文档。例如,对于一个包含 “product_type” 字段的商品数据集,我们可以使用 Terms 聚合按产品类型对商品进行分组,这样就能知道每种产品类型有多少个商品。
- 代码示例:
{ "aggs": { "product_type_buckets": { "terms": { "field": "product_type" } } } }
在上述示例中,“aggs” 是聚合的根节点,“product_type_buckets” 是我们给这个聚合起的名字,可以自定义。“terms” 表示使用 Terms 聚合类型,“field” 指明了要根据 “product_type” 字段进行分组。
-
Date Histogram 聚合
- 原理:Date Histogram 聚合主要用于日期类型的字段。它会根据指定的时间间隔对日期进行分组,将落在同一时间间隔内的文档划分到同一个桶中。比如,我们有一个包含 “order_date” 字段的订单数据集,我们可以按天、周、月等时间间隔对订单进行分组,统计每天、每周或每月的订单数量。
- 代码示例:
{ "aggs": { "order_date_buckets": { "date_histogram": { "field": "order_date", "calendar_interval": "day" } } } }
这里 “order_date_buckets” 是聚合名称,“date_histogram” 表明使用日期直方图聚合,“field” 为 “order_date” 日期字段,“calendar_interval” 设置为 “day” 表示按天进行分组。如果要按周分组,可以将 “calendar_interval” 设置为 “week”。
-
Range 聚合
- 原理:Range 聚合用于根据数值范围对文档进行分组。我们可以定义多个数值范围,文档会根据其某个数值字段的值被划分到相应的范围桶中。例如,对于一个包含 “price” 字段的商品数据集,我们可以设置不同的价格范围,如 0 - 100,101 - 200 等,统计每个价格范围内的商品数量。
- 代码示例:
{ "aggs": { "price_ranges": { "range": { "field": "price", "ranges": [ { "from": 0, "to": 100 }, { "from": 101, "to": 200 } ] } } } }
“price_ranges” 是聚合名称,“range” 表示使用 Range 聚合,“field” 为 “price” 价格字段,“ranges” 数组中定义了具体的价格范围。
存储桶聚合的嵌套与组合
嵌套聚合
在 Elasticsearch 中,我们可以在一个聚合中嵌套另一个聚合。这使得我们能够进行更复杂的数据分析。例如,我们先按 “product_type” 进行 Terms 聚合,然后在每个产品类型的桶内再按 “brand” 进行 Terms 聚合,这样就能了解每个产品类型下不同品牌的分布情况。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"brand_buckets": {
"terms": {
"field": "brand"
}
}
}
}
}
}
在这个例子中,“product_type_buckets” 是外层的 Terms 聚合,根据 “product_type” 字段分组。“aggs” 节点下的 “brand_buckets” 是内层的 Terms 聚合,在每个 “product_type” 桶内根据 “brand” 字段进一步分组。
组合聚合
除了嵌套聚合,我们还可以将不同类型的聚合组合使用。比如,我们可以先使用 Date Histogram 聚合按月份对订单进行分组,然后在每个月的桶内使用 Terms 聚合按 “customer_type” 对订单进行分组,这样就能了解每个月不同客户类型的订单分布情况。
- 代码示例:
{
"aggs": {
"monthly_orders": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
},
"aggs": {
"customer_type_buckets": {
"terms": {
"field": "customer_type"
}
}
}
}
}
}
这里 “monthly_orders” 是外层的 Date Histogram 聚合,按月份对 “order_date” 进行分组。内层的 “customer_type_buckets” 是 Terms 聚合,在每个月的桶内按 “customer_type” 分组。
存储桶聚合与文档过滤
基于文档过滤的聚合
在进行存储桶聚合时,我们常常需要先对文档进行过滤,只对符合特定条件的文档进行聚合操作。例如,我们只想统计价格大于 100 的商品按 “product_type” 的分组情况。这就需要使用过滤(Filter)与聚合结合的方式。
- 代码示例:
{
"query": {
"range": {
"price": {
"gt": 100
}
}
},
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
}
}
}
}
在这个例子中,“query” 部分使用 “range” 查询过滤出价格大于 100 的文档。然后 “aggs” 部分对这些过滤后的文档按 “product_type” 进行 Terms 聚合。
聚合内的过滤
除了在全局层面过滤文档,我们还可以在聚合内部进行过滤。例如,我们在按 “product_type” 进行 Terms 聚合时,只想统计 “product_type” 为 “electronics” 且价格大于 100 的文档数量。这可以通过 “filter” 子聚合来实现。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"electronics_over_100": {
"filter": {
"bool": {
"must": [
{
"term": {
"product_type": "electronics"
}
},
{
"range": {
"price": {
"gt": 100
}
}
}
]
}
}
}
}
}
}
}
这里 “product_type_buckets” 是按 “product_type” 进行的 Terms 聚合。在其内部的 “aggs” 中,“electronics_over_100” 是一个 “filter” 子聚合,它使用 “bool” 查询过滤出 “product_type” 为 “electronics” 且价格大于 100 的文档。
存储桶聚合的排序与限制
排序
在存储桶聚合的结果中,默认情况下,桶是按照文档数量降序排列的。但我们可以根据自己的需求进行排序。例如,对于 Terms 聚合,我们可以按桶的键(即字段的唯一值)升序排列,或者按某个指标(如文档数量)升序或降序排列。
- 按桶的键升序排列的代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type",
"order": {
"_key": "asc"
}
}
}
}
}
这里 “order” 部分指定了按 “_key”(即桶的键)升序排列。如果要降序排列,可以将 “asc” 改为 “desc”。
- 按文档数量升序排列的代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type",
"order": {
"_count": "asc"
}
}
}
}
}
这里 “_count” 表示文档数量,按文档数量升序排列。
限制
在某些情况下,我们可能只需要获取聚合结果中的前几个桶。例如,我们只想知道销量最高的前 5 种产品类型。这可以通过设置 “size” 参数来实现。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type",
"size": 5
}
}
}
}
上述示例中,“size” 设置为 5,这意味着只返回按默认排序(文档数量降序)的前 5 个产品类型的桶。
存储桶聚合的指标计算
在每个存储桶聚合生成的桶内,我们可以进一步计算各种指标,如平均值、总和、最大值、最小值等。这些指标能帮助我们更深入地了解每个分组的数据特征。
平均值指标
例如,我们按 “product_type” 进行 Terms 聚合后,想知道每个产品类型的平均价格。这可以通过在 Terms 聚合内添加 “avg” 指标聚合来实现。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
这里 “product_type_buckets” 是按 “product_type” 进行的 Terms 聚合,“average_price” 是在每个产品类型桶内计算平均价格的 “avg” 指标聚合,“field” 为 “price” 价格字段。
总和指标
如果我们想知道每个产品类型的总销售额(假设数据集中有 “price” 和 “quantity” 字段,销售额 = 价格 * 数量),可以使用 “sum” 指标聚合。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"total_sales": {
"sum": {
"script": {
"source": "doc['price'].value * doc['quantity'].value"
}
}
}
}
}
}
}
在这个例子中,“total_sales” 是计算总销售额的 “sum” 指标聚合,通过 “script” 脚本计算每个文档的销售额(价格乘以数量)并求和。
最大值与最小值指标
我们还可以获取每个产品类型中的最高价格和最低价格。
- 获取最大值的代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
}
}
- 获取最小值的代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
},
"aggs": {
"min_price": {
"min": {
"field": "price"
}
}
}
}
}
}
上述两个示例分别通过 “max” 和 “min” 指标聚合获取每个产品类型桶内的最大和最小价格。
存储桶聚合的深度分页
在处理大量数据的聚合时,可能会遇到需要获取聚合结果的多页数据的情况。这就涉及到深度分页。Elasticsearch 提供了两种主要的方式来处理深度分页:“from” 和 “size” 参数以及 “search_after”。
“from” 和 “size” 参数
这是最基本的分页方式。“from” 表示从结果集的第几项开始返回,“size” 表示返回的结果数量。例如,我们想获取按 “product_type” 进行 Terms 聚合结果的第 11 - 20 项。
- 代码示例:
{
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type",
"from": 10,
"size": 10
}
}
}
}
这里 “from” 设置为 10(从第 11 项开始,因为索引从 0 开始),“size” 设置为 10,表示返回 10 项。然而,这种方式在处理深度分页时性能较差,因为 Elasticsearch 需要在每个分片上计算完整的排序结果,然后再合并。
“search_after”
“search_after” 是一种更高效的深度分页方式。它通过使用上一页的最后一个文档的排序值来获取下一页的数据。例如,我们先获取第一页数据,假设最后一个文档的 “price” 字段值为 150,我们可以使用这个值来获取下一页数据。
- 获取第一页数据的代码示例:
{
"sort": [
{
"price": {
"order": "asc"
}
}
],
"size": 10,
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
}
}
}
}
- 获取第二页数据的代码示例:
{
"sort": [
{
"price": {
"order": "asc"
}
}
],
"search_after": [150],
"size": 10,
"aggs": {
"product_type_buckets": {
"terms": {
"field": "product_type"
}
}
}
}
在获取第二页数据时,“search_after” 设置为第一页最后一个文档的 “price” 值(这里假设为 150),这样 Elasticsearch 可以根据这个值更高效地获取下一页数据,避免了在每个分片上计算完整排序结果的开销。
存储桶聚合在实际场景中的应用
电商数据分析
在电商场景中,存储桶聚合有着广泛的应用。例如,按商品类别(“category” 字段)进行 Terms 聚合,统计每个类别下的商品数量,了解不同类别商品的受欢迎程度。同时,可以在每个类别桶内计算平均价格、总销售额等指标,分析不同类别商品的销售情况。
- 代码示例:
{
"aggs": {
"category_buckets": {
"terms": {
"field": "category"
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
},
"total_sales": {
"sum": {
"script": {
"source": "doc['price'].value * doc['quantity'].value"
}
}
}
}
}
}
}
通过这样的聚合操作,电商运营人员可以了解哪些商品类别更受欢迎,哪些类别虽然商品数量少但销售额高,从而制定更合理的商品策略。
日志分析
在日志分析场景中,假设我们有一个包含 “timestamp”(时间戳)和 “log_level”(日志级别)字段的日志数据集。我们可以使用 Date Histogram 聚合按小时对日志进行分组,然后在每个小时桶内使用 Terms 聚合按日志级别统计日志数量,了解不同时间段内不同日志级别的分布情况。
- 代码示例:
{
"aggs": {
"hourly_logs": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "hour"
},
"aggs": {
"log_level_buckets": {
"terms": {
"field": "log_level"
}
}
}
}
}
}
通过这种方式,运维人员可以快速定位哪些时间段内出现了较多的错误日志(假设 “log_level” 有 “error”、“warning” 等级别),以便及时排查问题。
社交媒体数据分析
在社交媒体场景中,假设我们有用户发布内容的数据集,包含 “user_type”(用户类型,如普通用户、认证用户)、“post_time”(发布时间)和 “engagement”(互动量,如点赞数、评论数之和)字段。我们可以先按用户类型进行 Terms 聚合,然后在每个用户类型桶内使用 Date Histogram 聚合按天对发布时间进行分组,并计算每天的平均互动量。
- 代码示例:
{
"aggs": {
"user_type_buckets": {
"terms": {
"field": "user_type"
},
"aggs": {
"daily_engagement": {
"date_histogram": {
"field": "post_time",
"calendar_interval": "day"
},
"aggs": {
"average_engagement": {
"avg": {
"field": "engagement"
}
}
}
}
}
}
}
}
这样,社交媒体运营人员可以了解不同类型用户每天的内容互动情况,从而制定更有针对性的运营策略,提高用户参与度。
通过以上对 Elasticsearch 存储桶聚合的详细介绍,包括基础概念、各种聚合类型、嵌套与组合、过滤、排序、指标计算、深度分页以及实际应用场景等方面,相信读者对存储桶聚合有了全面而深入的理解,能够在实际的数据分析工作中灵活运用这一强大的功能。