ES(ElasticSearch)-聚合查询

聚合查询

相关概念

桶(Buckets):满足特定条件的文档集合。可以理解为 SQL 中的 GROUP BY。例如:“北京”是一个桶,“上海”也是一个桶。

指标(Metrics):对桶内的文档进行的统计计算。可以理解为 SQL 中的 COUNT(), SUM(), AVG() 等。例如:计算北京这个桶里有多少人(计数),平均薪资是多少(求平均值)。

聚合查询:其实就是通和指标的各种组合嵌套的统计查询

聚合查询的语法结构

一个标准的聚合查询 (aggs) 通常与查询 (query) 同级,用于在全数据集或查询结果集上进行聚合分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
GET /your_index/_search
{
"size": 0, // 通常我们不关心具体的命中文档,只关心聚合结果,所以设为0以提高性能
"query": { // (可选)先执行查询,再对查询结果进行聚合
... // 例如 match, term, range 等查询
},
"aggs": { // 聚合查询的主体,也可以简写为 "aggregations"
"给你的聚合结果起个名字": { // 例如 "avg_salary", "group_by_city"
"聚合类型": { // 例如 "terms", "avg", "date_histogram"
"聚合体": ... // 不同类型有不同的参数,如 "field": "salary"
}
},
// ... 可以同时有多个同级聚合
// 也可以有子聚合
"另一个聚合": {
...
"aggs": { // 在当前聚合的桶内,继续进行嵌套聚合
"子聚合的名字": {
"子聚合类型": {
...
}
}
}
}
}
}

举例:

目标:先找出所有“Electronics”类别的商品(查询),然后按品牌(brand)分组(桶聚合),并计算每个品牌的平均价格(指标聚合)。

**查询请求 (Request)**:

json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /sales/_search
{
"size": 0, // 不关心具体文档,只关心聚合结果
"query": {
"term": {
"category": "electronics" // 先过滤出电子类商品
}
},
"aggs": {
"group_by_brand": { // 第一层聚合:按品牌分组
"terms": {
"field": "brand.keyword"
},
"aggs": { // 在每一个品牌桶内,做子聚合
"avg_price": { // 第二层聚合:计算平均价格
"avg": {
"field": "price"
}
}
}
}
}
}

**预期的聚合结果 (Response)**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"took": 5,
"timed_out": false,
"_shards": { ... },
"hits": { // 命中文档部分(因为 size=0,所以只有总数)
"total": {
"value": 100, // 一共有100个电子类商品文档
"relation": "eq"
},
"max_score": null,
"hits": [] // 具体的文档列表为空
},
"aggregations": { // !!!聚合结果部分 !!!
"group_by_brand": { // 对应请求中 "group_by_brand" 聚合的结果
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [ // 这是一个桶聚合,所以核心是一个 buckets 数组
{
"key": "Apple", // 第一个桶的键(品牌名)
"doc_count": 35, // 属于Apple品牌的电子商品有35个
"avg_price": { // !!!这是子聚合的结果 !!!
"value": 899.99 // Apple品牌的平均价格是899.99
}
},
{
"key": "Samsung",
"doc_count": 30,
"avg_price": {
"value": 749.99
}
},
{
"key": "Sony",
"doc_count": 20,
"avg_price": {
"value": 649.99
}
},
... // 其他桶
]
}
}
}

group_by_brand、avg_price这两个聚合名称在结果里也会按层次出现,解析结果是需要识别自行业务处理。

聚合查询分类

假设我们有一个索引 sales,存储着商品销售记录,包含以下字段:

  • product (keyword): 产品名称,如 “iPhone 15”, “Coffee Maker”
  • category (keyword): 产品类别,如 “Electronics”, “Beverages”
  • price (double): 价格
  • sold_date (date): 销售日期
  • region (keyword): 销售地区,如 “North”, “South”

指标聚合(Metrics Aggregations)

对数据集进行计算,输出一个或多个值。

平均值(Avg)

  • 描述:计算一个字段的平均值。
  • 示例:计算所有产品的平均售价
1
2
3
4
5
6
7
8
9
10
11
GET /sales/_search
{
"size": 0,
"aggs": {
"avg_price": { // 给你的聚合起个名
"avg": { // 聚合类型是 avg
"field": "price" // 对 price 字段求平均
}
}
}
}

结果

1
2
3
4
5
6
7
8
{
...
"aggregations" : {
"avg_price" : {
"value" : 245.72 // 这就是计算出的平均值
}
}
}

求和(Sum)

  • 描述:计算一个字段的总和。
  • 示例:计算所有产品的销售总额。
1
2
3
4
5
6
7
8
9
10
11
GET /sales/_search
{
"size": 0,
"aggs": {
"total_sales": {
"sum": {
"field": "price"
}
}
}
}

最大值(Max)与最小值(Min)

  • 描述:找出一个字段的最大或最小值。
  • 示例:找出最贵的产品价格。

json

1
2
3
4
5
6
7
8
9
10
11
GET /sales/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {//min
"field": "price"
}
}
}
}

统计(Stats)

  • 描述:一次性返回 count, max, min, avg, sum 五个基本指标。
  • 示例:获取价格的基本统计信息。

总结

  • 指标聚合是计算,输出的是数值。
  • avg, sum, max, min 是单值指标聚合。
  • stats 是多值指标聚合,非常方便,一次获取所有常用统计值。

桶聚合(Bucket Aggregations)

将文档分组到不同的桶中

词条聚合(Terms)

  • 描述:按某个字段的确切值进行分组,是最常用的桶聚合。
  • 示例:按产品类别进行分组,看每个类别有多少商品。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
GET /sales/_search
{
"size": 0,
"aggs": {
"group_by_category": { // 按类别分组
"terms": {
"field": "category", // 分组的依据字段
"size": 20 // 返回排名前20的桶,默认是10
}
}
}
}
//结果
{
...
"aggregations" : {
"group_by_category" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 120, // 除了返回的桶之外,其他所有类别的文档总数
"buckets" : [ // 桶的集合,默认按 doc_count 降序排列
{
"key" : "Electronics", // 桶的键,即类别名称
"doc_count" : 85 // 属于该桶的文档数量
},
{
"key" : "Beverages",
"doc_count" : 63
},
{
"key" : "Books",
"doc_count" : 42
}
]
}
}
}

范围聚合(Range)

  • 描述:按数值范围创建桶。
  • 示例:按价格区间分组(0-50, 50-100, 100-500, 500+)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
GET /sales/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 50 }, // 价格 < 50
{ "from": 50, "to": 100 }, // 50 <= 价格 < 100
{ "from": 100, "to": 500 },
{ "from": 500 }
]
}
}
}
}

//结果
{
...
"aggregations" : {
"price_ranges" : {
"buckets" : [
{
"key" : "*-50.0", // ES自动生成的key,也可以使用 "keyed": true 来自定义
"to" : 50.0,
"doc_count" : 128 // 价格 < 50 的商品数量
},
{
"key" : "50.0-100.0",
"from" : 50.0,
"to" : 100.0,
"doc_count" : 204
},
{
"key" : "100.0-500.0",
"from" : 100.0,
"to" : 500.0,
"doc_count" : 312
},
{
"key" : "500.0-*",
"from" : 500.0,
"doc_count" : 56
}
]
}
}
}

日期直方图聚合(Date Histogram)

  • 描述:按固定的时间间隔(如每天、每月)创建桶。处理时间序列数据必备
  • 示例:统计每月有多少销售额。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "sold_date",
"calendar_interval": "month", // 时间间隔:月。也可以是 "day", "year"
"format": "yyyy-MM" // 返回结果的日期格式(可选)
}
}
}
}

//聚合
{
...
"aggregations" : {
"sales_per_month" : {
"buckets" : [
{
"key_as_string" : "2024-01", // 格式化后的日期字符串
"key" : 1704067200000, // 时间戳(毫秒)
"doc_count" : 150 // 2024年1月销售的文档数
},
{
"key_as_string" : "2024-02",
"key" : 1706745600000,
"doc_count" : 183
},
{
"key_as_string" : "2024-03",
"key" : 1709251200000,
"doc_count" : 210
}
// ... 其他月份
]
}
}
}

组合聚合:桶内嵌套指标

查询:计算每个产品类别的平均价格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
GET /sales/_search
{
"size": 0,
"aggs": {
"group_by_category": {
"terms": {
"field": "category"
},
"aggs": {
"avg_price_in_category": {
"avg": {
"field": "price"
}
}
}
}
}
}
//结果
{
...
"aggregations" : {
"group_by_category" : {
"buckets" : [
{
"key" : "Electronics",
"doc_count" : 85,
"avg_price_in_category" : { // 子聚合的结果以其名称命名
"value" : 599.99 // 电子类产品的平均价格
}
},
{
"key" : "Beverages",
"doc_count" : 63,
"avg_price_in_category" : {
"value" : 5.99 // 饮品类产品的平均价格
}
},
{
"key" : "Books",
"doc_count" : 42,
"avg_price_in_category" : {
"value" : 25.49
}
}
]
}
}
}

在每个类别桶(key)的内部,除了 doc_count,还多出了一个以子聚合名称(avg_price_in_category)命名的对象,其中包含了计算出的指标 value

复杂组合聚合:多层嵌套

查询:先按地区分组,再按月份分组,然后计算每个地区-月组合的销售总额。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
GET /sales/_search
{
"size": 0,
"aggs": {
"group_by_region": {
"terms": {
"field": "region"
},
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sold_date",
"calendar_interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"monthly_revenue": {
"sum": {
"field": "price"
}
}
}
}
}
}
}
}
//结果
{
...
"aggregations" : {
"group_by_region" : {
"buckets" : [
{
"key" : "North", // 第一个地区桶
"doc_count" : 400,
"sales_over_time" : { // 该地区下的日期直方图子聚合
"buckets" : [ // 这个buckets是North地区下的月份桶
{
"key_as_string" : "2024-01",
"key" : 1704067200000,
"doc_count" : 120,
"monthly_revenue" : { // 月份桶内的求和子聚合
"value" : 35880.50 // North地区2024年1月的总销售额
}
},
{
"key_as_string" : "2024-02",
"key" : 1706745600000,
"doc_count" : 145,
"monthly_revenue" : {
"value" : 42123.20
}
}
// ... North地区的其他月份
]
}
},
{
"key" : "South", // 第二个地区桶
"doc_count" : 350,
"sales_over_time" : {
"buckets" : [ // South地区下的月份桶
{
"key_as_string" : "2024-01",
"doc_count" : 110,
"monthly_revenue" : {
"value" : 22500.00
}
}
// ... South地区的其他月份
]
}
}
]
}
}
}
  • “分层下钻”:先分大组(桶),再在组内进行计算(指标)或再分小组(桶)。
  • 可以无限嵌套,但性能会随着嵌套深度增加而下降。
  • 这是构建复杂数据分析报表的基础。

这个结果完美展示了数据的“分层下钻”结构:

  1. 第一层:region 桶(North, South)。
  2. 在每一个 region 桶内,是第二层:date_histogram 桶(2024-01, 2024-02…)。
  3. 在每一个 日期 桶内,是最终计算的指标:该月该地区的销售

-