本文目录导读:

这是一个非常实用的话题,Elasticsearch 的聚合(Aggregation)功能非常强大,可以从数据中提取各种统计信息,而不仅仅是简单的搜索。
下面我将从基础概念到实战案例,为你系统性地讲解 ES 聚合查询,并提供可直接运行的案例。
核心概念(必懂)
在写案例之前,先了解这几个关键词:
aggs:查询请求中的聚合关键字。metric:指标聚合,用来计算数值,如sum、avg、max、min、cardinality(去重计数)。bucket:分桶聚合,类似 SQL 中的GROUP BY,将文档分组到不同的桶中,如terms(按字段值分)、date_histogram(按日期分)、range(按范围分)。sub-aggs:子聚合,在桶内再次进行聚合,可以嵌套。
基础环境准备(Kibana Dev Tools)
为了方便演示,我们先建立索引并插入测试数据(假设是一个电商订单系统)。
# 1. 创建索引并指定映射(mapping)
PUT /orders
{
"mappings": {
"properties": {
"order_id": { "type": "keyword" },
"customer": { "type": "keyword" },
"product": { "type": "keyword" },
"category": { "type": "keyword" },
"price": { "type": "float" },
"quantity": { "type": "integer" },
"order_date": { "type": "date" }
}
}
}
# 2. 批量插入测试数据
POST /_bulk
{ "index": { "_index": "orders" } }
{ "order_id": "1001", "customer": "Alice", "product": "iphone 15", "category": "Electronics", "price": 999, "quantity": 1, "order_date": "2024-01-15" }
{ "index": { "_index": "orders" } }
{ "order_id": "1002", "customer": "Bob", "product": "MacBook Pro", "category": "Electronics", "price": 1999, "quantity": 1, "order_date": "2024-01-20" }
{ "index": { "_index": "orders" } }
{ "order_id": "1003", "customer": "Alice", "product": "Desk Chair", "category": "Furniture", "price": 150, "quantity": 2, "order_date": "2024-02-05" }
{ "index": { "_index": "orders" } }
{ "order_id": "1004", "customer": "Charlie", "product": "Standing Desk", "category": "Furniture", "price": 450, "quantity": 1, "order_date": "2024-02-10" }
{ "index": { "_index": "orders" } }
{ "order_id": "1005", "customer": "Bob", "product": "Gaming Mouse", "category": "Electronics", "price": 99, "quantity": 3, "order_date": "2024-03-18" }
{ "index": { "_index": "orders" } }
{ "order_id": "1006", "customer": "David", "product": "Coffee Maker", "category": "Appliances", "price": 80, "quantity": 1, "order_date": "2024-03-22" }
实战案例(由浅入深)
案例 1:统计销售额与平均价格(Metric)
需求:计算所有订单的总销售额、平均单价、最大和最小订单金额。
销售额 = 单价 * 数量,这里我们先计算总价(需要用script计算),或者简化逻辑直接对price求sum。
GET /orders/_search
{
"size": 0,
"aggs": {
"total_sales": {
"sum": { "field": "price" }
},
"avg_price": {
"avg": { "field": "price" }
},
"max_price": {
"max": { "field": "price" }
},
"min_price": {
"min": { "field": "price" }
}
}
}
返回结果:
{
"aggregations": {
"total_sales": { "value": 3777.0 },
"avg_price": { "value": 629.5 },
"max_price": { "value": 1999.0 },
"min_price": { "value": 80.0 }
}
}
技巧:
"size": 0代表不返回具体文档,只返回聚合结果,提高性能。
案例 2:按商品类目分组,统计每个类目的销售额(Terms Bucket)
需求:类似于 SQL 的 SELECT category, SUM(price) FROM orders GROUP BY category。
GET /orders/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"sales_amount": {
"sum": { "field": "price" }
}
}
}
}
}
返回结果(部分):
"by_category": {
"buckets": [
{ "key": "Electronics", "doc_count": 3, "sales_amount": { "value": 3097.0 } },
{ "key": "Furniture", "doc_count": 2, "sales_amount": { "value": 600.0 } },
{ "key": "Appliances", "doc_count": 1, "sales_amount": { "value": 80.0 } }
]
}
案例 3:按月份统计销售趋势(Date Histogram)
需求:查看每月销售额的趋势。
GET /orders/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
},
"aggs": {
"total_revenue": {
"sum": {
"script": {
"source": "doc['price'].value * doc['quantity'].value"
}
}
}
}
}
}
}
说明:这里使用了 script 来计算 单价 * 数量,更加真实。
返回结果(部分):
"sales_over_time": {
"buckets": [
{ "key_as_string": "2024-01-01", "key": 1704067200000, "doc_count": 2, "total_revenue": { "value": 2998.0 } },
{ "key_as_string": "2024-02-01", "key": 1706745600000, "doc_count": 2, "total_revenue": { "value": 750.0 } },
{ "key_as_string": "2024-03-01", "key": 1709251200000, "doc_count": 2, "total_revenue": { "value": 377.0 } }
]
}
案例 4:Top N 客户(排序案例)
需求:找出消费最多的前 3 名客户。
GET /orders/_search
{
"size": 0,
"aggs": {
"top_customers": {
"terms": {
"field": "customer",
"size": 3,
"order": { "total_spent": "desc" }
},
"aggs": {
"total_spent": {
"sum": {
"script": { "source": "doc['price'].value * doc['quantity'].value" }
}
}
}
}
}
}
返回:客户 Bob(1999+297=2296)排第一,Alice(999+300=1299)排第二。
案例 5:价格分布区间(Range Bucket)
需求:统计价格在 0-100,100-500,500 以上的订单数量。
GET /orders/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "key": "Under $100", "to": 100 },
{ "key": "$100-$500", "from": 100, "to": 500 },
{ "key": "Over $500", "from": 500 }
]
}
}
}
}
返回:Under $100 有 2 单(99,80),$100-$500 有 2 单(150,450),Over $500 有 2 单(999,1999)。
案例 6:嵌套聚合与百分比(子聚合 + 去重计数)
需求:统计每个类目下的在线商品数量(去重)和销量总和。
GET /orders/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category" },
"aggs": {
"unique_products": {
"cardinality": { "field": "product" }
},
"total_quantity": {
"sum": { "field": "quantity" }
}
}
}
}
}
高级技巧与注意事项
size与shard_size:默认terms聚合只返回前 10 个桶,如果要统计全局 Top 10,建议设置shard_size(如 10000),防止分片返回数据不全导致统计偏差。- 过滤与聚合:可以利用
filter在聚合前缩小范围,聚合aggs和查询query是并行的,query会先于aggs执行,aggs的结果是基于query的结果集计算的。// 例子:只统计 2024 年 1 月之后的电子类目销售 GET /orders/_search { "query": { "bool": { "filter": [{ "range": { "order_date": { "gte": "2024-01-01" } } }] } }, "size": 0, "aggs": { "electronics_sales": { "filter": { "term": { "category": "Electronics" } }, "aggs": { "total": { "sum": { "field": "price" } } } } } } sum需要开启 doc_values:默认启用,但如果是text字段需要改为keyword类型才能聚合。- 性能优化:聚合非常耗费内存,如果聚合字段不需要分词,尽量使用
keyword类型,避免在text字段上聚合。
案例覆盖了 ES 聚合的 80% 核心场景:
- 求和平均值 ->
metric聚合 - 分类统计 ->
terms聚合 - 时间趋势 ->
date_histogram聚合 - 范围分布 ->
range聚合 - 去重统计 ->
cardinality聚合
掌握这些,配合嵌套(nested 或子聚合),你基本上可以应对大部分 BI 报表和业务分析需求了。
如果你有具体的业务场景(搜索某关键词后的商品点击占比”),可以告诉我,我可以给出更精准的 DSL 语法。