在掌握了基础查询和全文搜索后,我们将探索 Elasticsearch 最强大的数据分析功能——聚合分析。本文将带你深入理解聚合的核心概念、三种聚合类型及实战应用,让你从数据中提取有价值的商业洞察。
Elasticsearch 聚合是一种强大的数据分析工具,它能够从索引中提取和计算复杂的统计信息。与传统数据库的 GROUP BY 相比,Elasticsearch 的聚合更灵活、更强大,可以处理海量数据并实现实时分析。
聚合的核心思想类似于 SQL 中的分组统计,但功能更为丰富。它允许我们对搜索结果进行多维度、多条件的数据分析和汇总,从而更好地理解数据特征和趋势。
理解聚合需要掌握两个基本概念:
简单比喻:假设我们有一群人,按照性别分组(桶),然后计算每个组的平均年龄(指标)。
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" } } } } } }
参数说明:
指标聚合用于计算数值型统计结果,返回单个值或多个值。
常用指标聚合类型:
| 聚合类型 | 描述 | 对应SQL |
|---|---|---|
| avg | 计算字段的平均值 | AVG() |
| sum | 计算字段的总和 | SUM() |
| min/max | 查找字段的最小/最大值 | MIN()/MAX() |
| stats | 同时返回count、sum、min、max、avg | 多个函数组合 |
| cardinality | 计算字段不同值的数量 | COUNT(DISTINCT) |
| percentiles | 计算字段的百分位数 | 无直接对应 |
示例:计算商品价格统计信息
json GET /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" } } } } }
参数说明:
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" } } } }
管道聚合以其他聚合的结果作为输入,进行二次计算。
常用管道聚合:
电商数据分析需求:分析每个品牌的商品数量、平均价格和价格统计信息。
json GET /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 } ] } } } } } }
员工信息分析需求:分析公司不同年龄段员工的平均薪资和性别分布。
json GET /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 )); }
通过本文,我们深入学习了:
聚合分析的价值:
聚合分析是 Elasticsearch 最强大的功能之一,掌握它能够让你从数据中发现隐藏的价值,为业务决策提供有力支持。
本文作者:张豪
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!