在掌握了基础查询和全文搜索后,我们将探索 Elasticsearch 最强大的数据分析功能——聚合分析。本文将带你深入理解聚合的核心概念、三种聚合类型及实战应用,让你从数据中提取有价值的商业洞察。
Elasticsearch 聚合是一种强大的数据分析工具,它能够从索引中提取和计算复杂的统计信息。与传统数据库的 GROUP BY 相比,Elasticsearch 的聚合更灵活、更强大,可以处理海量数据并实现实时分析。
聚合的核心思想类似于 SQL 中的分组统计,但功能更为丰富。它允许我们对搜索结果进行多维度、多条件的数据分析和汇总,从而更好地理解数据特征和趋势。
理解聚合需要掌握两个基本概念:
桶(Buckets):满足特定条件的文档集合,相当于 SQL 中的分组
指标(Metrics):对桶内文档计算的统计信息,如总和、平均值、最大值等
简单比喻:假设我们有一群人,按照性别分组(桶),然后计算每个组的平均年龄(指标)。
Elasticsearch 的聚合语法基于 JSON 格式,基本结构如下:
json{
"size": 0, // 不返回原始搜索结果,只关注聚合结果
"aggs": { // 聚合查询的固定关键字
"aggregation_name": { // 自定义聚合名称
"aggregation_type": { // 聚合类型(terms、date_histogram等)
"field": "field_name" // 要聚合的字段
},
"aggs": { // 嵌套子聚合(可选)
"sub_aggregation_name": {
"aggregation_type": {
"field": "field_name"
}
}
}
}
}
}
参数说明:
size: 0 表示只返回聚合结果,不返回搜索命中的文档
聚合支持嵌套,允许构建复杂的数据分析逻辑
指标聚合用于计算数值型统计结果,返回单个值或多个值。
常用指标聚合类型:
| 聚合类型 | 描述 | 对应SQL |
|---|---|---|
| avg | 计算字段的平均值 | AVG() |
| sum | 计算字段的总和 | SUM() |
| min/max | 查找字段的最小/最大值 | MIN()/MAX() |
| stats | 同时返回count、sum、min、max、avg | 多个函数组合 |
| cardinality | 计算字段不同值的数量 | COUNT(DISTINCT) |
| percentiles | 计算字段的百分位数 | 无直接对应 |
示例:计算商品价格统计信息
jsonGET /products/_search
{
"size": 0,
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
},
"distinct_brands": {
"cardinality": {
"field": "brand.keyword"
}
}
}
}
桶聚合将文档分组到不同的桶中,每个桶对应一个特定的条件或范围。
常用桶聚合类型:
Terms 聚合 - 按字段值分组
json// 按品牌分组统计商品数量
GET /products/_search
{
"size": 0,
"aggs": {
"brands": {
"terms": {
"field": "brand.keyword",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}
参数说明:
size:指定返回的桶数量
order:指定桶的排序方式
Range 聚合 - 按数值范围分组
json// 按价格区间统计商品
GET /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 500 },
{ "from": 500 }
]
}
}
}
}
Date Histogram 聚合 - 按时间间隔分组
json// 按月统计销售记录
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "month",
"format": "yyyy-MM"
}
}
}
}
管道聚合以其他聚合的结果作为输入,进行二次计算。
常用管道聚合:
avg_bucket:计算桶的平均值
sum_bucket:计算桶的总和
max_bucket/min_bucket:找出桶的最大值/最小值
bucket_script:对多个聚合结果执行脚本计算
电商数据分析需求:分析每个品牌的商品数量、平均价格和价格统计信息。
jsonGET /products/_search
{
"size": 0,
"aggs": {
"brand_analysis": {
"terms": {
"field": "brand.keyword",
"size": 5
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"price_stats": {
"stats": { "field": "price" }
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 1000 },
{ "from": 1000 }
]
}
}
}
}
}
}
员工信息分析需求:分析公司不同年龄段员工的平均薪资和性别分布。
jsonGET /employees/_search
{
"size": 0,
"aggs": {
"age_ranges": {
"range": {
"field": "age",
"ranges": [
{ "from": 20, "to": 30 },
{ "from": 30, "to": 40 },
{ "from": 40, "to": 50 },
{ "from": 50 }
]
},
"aggs": {
"avg_salary": {
"avg": { "field": "salary" }
},
"gender_distribution": {
"terms": { "field": "gender.keyword" }
}
}
}
}
}
计算字段空值率需求:统计某个字段的空值率
json{
"size": 0,
"aggs": {
"all_documents": {
"terms": {
"script": "return 'all_documents';"
},
"aggs": {
"total_count": {
"value_count": { "field": "_id" }
},
"non_empty_count": {
"value_count": {
"script": "if (doc['my_field'].size() != 0 && doc['my_field'].value != '') return 1"
}
},
"empty_percentage": {
"bucket_script": {
"buckets_path": {
"total": "total_count",
"nonEmpty": "non_empty_count"
},
"script": "(1 - params.nonEmpty / params.total) * 100"
}
}
}
}
}
}
Elasticsearch 聚合操作的性能很大程度上依赖于底层数据结构:
Doc Values:列式存储,适用于精确值字段,默认启用,推荐使用
Fielddata:基于内存的数据结构,用于 text 字段,谨慎使用(因为可能消耗大量堆内存,在处理大数据集时容易引发内存溢出(OOM)问题)
使用 keyword 类型进行聚合
对于文本字段,使用 .keyword 子字段而非原始 text 字段:
json// 推荐 ✅
"terms": { "field": "brand.keyword" }
// 避免 ❌
"terms": { "field": "brand" }
合理设置聚合大小
json"terms": {
"field": "category.keyword",
"size": 10 // 限制返回的桶数量
}
结合查询条件使用聚合
json{
"query": {
"range": { "price": { "gte": 100 } }
},
"aggs": {
// 只在价格>=100的商品上进行聚合
}
}
避免在分片字段上使用深度分页聚合
在分片数较多的索引上,使用 terms 聚合获取大量桶时可能不准确。
java// 使用 Java High Level REST Client 进行聚合查询
SearchRequest searchRequest = new SearchRequest("products");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构建品牌分析聚合
TermsAggregationBuilder brandAggregation = AggregationBuilders.terms("brand_analysis")
.field("brand.keyword")
.size(10);
// 添加子聚合 - 平均价格
AvgAggregationBuilder avgPriceAggregation = AggregationBuilders.avg("avg_price")
.field("price");
brandAggregation.subAggregation(avgPriceAggregation);
// 添加子聚合 - 价格统计
StatsAggregationBuilder priceStatsAggregation = AggregationBuilders.stats("price_stats")
.field("price");
brandAggregation.subAggregation(priceStatsAggregation);
sourceBuilder.aggregation(brandAggregation);
sourceBuilder.size(0); // 只返回聚合结果
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理聚合结果
Terms brandTerms = response.getAggregations().get("brand_analysis");
for (Terms.Bucket bucket : brandTerms.getBuckets()) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
Avg avgPrice = bucket.getAggregations().get("avg_price");
double averagePrice = avgPrice.getValue();
Stats priceStats = bucket.getAggregations().get("price_stats");
double minPrice = priceStats.getMin();
double maxPrice = priceStats.getMax();
System.out.println(String.format(
"品牌: %s, 商品数: %d, 平均价格: %.2f, 价格范围: %.2f-%.2f",
brand, docCount, averagePrice, minPrice, maxPrice
));
}
通过本文,我们深入学习了:
聚合核心概念:桶(Buckets)和指标(Metrics)
三种聚合类型:指标聚合、桶聚合、管道聚合
实战应用场景:电商分析、员工统计、空值率计算等
性能优化技巧:使用 keyword 字段、合理设置聚合大小等
聚合分析的价值:
业务洞察:从海量数据中提取有价值的商业信息
实时分析:支持大规模数据的实时统计分析
多维度分析:支持嵌套聚合,实现复杂的数据分析需求
聚合分析是 Elasticsearch 最强大的功能之一,掌握它能够让你从数据中发现隐藏的价值,为业务决策提供有力支持。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!