文章目录
- 官方文档
- Bucket Script 官文
- 1. 什么是 ElasticSearch 中的 Bucket Script?
- 2. 适用场景
- 3. Bucket Script 的基本结构
- 4. 关键参数详解
- 5. 示例
- 官方示例:计算每月 T 恤销售额占总销售额的比率百分比
- 示例计算:点击率 (CTR)
- 6. 注意事项与限制
- 7. 最佳实践
官方文档
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
Bucket Script 官文
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html
- 介绍 Bucket Script 的概念和作用
- 展示基本使用场景,帮助理解其核心原理
- 通过实例展示如何实现 Bucket Script
- 总结关键要点与最佳实践
1. 什么是 ElasticSearch 中的 Bucket Script?
Bucket Script 是 ElasticSearch 中一种强大的管道聚合(pipeline aggregation),允许你基于已有的聚合结果执行数学计算。 它用于对多个 桶(buckets) 内的数据进行后处理,适合在聚合结果上进行进一步计算,比如计算比率、加权平均等。
2. 适用场景
- 计算字段的 百分比(如收入增长率)
- 生成两个字段之间的 比值(如点击率 CTR)
- 在聚合结果中求得更复杂的 数学表达式
- 处理基于时间序列的数据分析,例如 同比、环比 增长计算
3. Bucket Script 的基本结构
Bucket Script 聚合的基本结构如下:
{
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
},
"total_units": {
"sum": {
"field": "units_sold"
}
},
"sales_per_unit": {
"bucket_script": {
"buckets_path": {
"sales": "total_sales",
"units": "total_units"
},
"script": "params.sales / params.units"
}
}
}
}
}
}
sales_per_month
:使用date_histogram
按月份进行分桶。total_sales
和total_units
:分别计算总销售额和总售出单位数。sales_per_unit
:使用bucket_script
在每个桶内计算销售额与售出单位的比值。
4. 关键参数详解
buckets_path
:指定需要参与计算的聚合结果路径,路径指向的聚合必须出现在当前或上层的桶中。script
:定义计算逻辑,使用 Painless 脚本语言 编写。
5. 示例
官方示例:计算每月 T 恤销售额占总销售额的比率百分比
PUT /sales
{
"mappings": {
"properties": {
"type": {
"type": "keyword"
},
"price": {
"type": "float"
},
"date": {
"type": "date"
}
}
}
}
POST /sales/_bulk
{ "index": { "_index": "sales" } }
{ "type": "t-shirt", "price": 19.99, "date": "2024-01-05" }
{ "index": { "_index": "sales" } }
{ "type": "t-shirt", "price": 25.50, "date": "2024-01-15" }
{ "index": { "_index": "sales" } }
{ "type": "jeans", "price": 49.99, "date": "2024-01-20" }
{ "index": { "_index": "sales" } }
{ "type": "t-shirt", "price": 15.99, "date": "2024-02-01" }
{ "index": { "_index": "sales" } }
{ "type": "shoes", "price": 75.00, "date": "2024-02-10" }
{ "index": { "_index": "sales" } }
{ "type": "t-shirt", "price": 29.99, "date": "2024-02-15" }
POST sales/_search
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": {
"field": "price"
}
},
"t-shirts": {
"filter": {
"term": {
"type": "t-shirt"
}
},
"aggs": {
"sales": {
"sum": {
"field": "price"
}
}
}
},
"t-shirt-percentage": {
"bucket_script": {
"buckets_path": {
"tShirtSales": "t-shirts>sales",
"totalSales": "total_sales"
},
"script": "params.tShirtSales / params.totalSales * 100"
}
}
}
}
}
}
此查询的目的是:
- 统计每个月的总销售额。
- 计算“T-shirt”类型商品的销售额。
- 计算“T-shirt”销售额占总销售额的百分比。
-
“size”: 0
- 表示这次查询不返回任何文档,仅返回聚合结果。
-
聚合:sales_per_month
- 使用
date_histogram
来按月对销售数据进行分桶:"date_histogram": { "field": "date", "calendar_interval": "month" }
- 字段
date
决定销售的日期。calendar_interval
设置为"month"
,意味着每个月作为一个桶。
- 使用
-
聚合:total_sales
- 计算每个月的总销售额:
"total_sales": { "sum": { "field": "price" } }
- 字段
price
表示商品价格,通过sum
聚合计算总和。
- 计算每个月的总销售额:
-
过滤聚合:t-shirts
- 使用
filter
过滤出类型为t-shirt
的销售:"filter": { "term": { "type": "t-shirt" } }
- 嵌套的sum聚合 计算T-shirt类型商品的销售额:
"sales": { "sum": { "field": "price" } }
- 使用
-
桶脚本聚合:t-shirt-percentage
- 计算T-shirt销售额占总销售额的百分比:
"bucket_script": { "buckets_path": { "tShirtSales": "t-shirts>sales", "totalSales": "total_sales" }, "script": "params.tShirtSales / params.totalSales * 100" }
buckets_path
用于从其他聚合中引用路径:"tShirtSales"
引用的是t-shirts>sales
聚合。"totalSales"
引用的是total_sales
聚合。
script
执行的逻辑是:T-shirt销售额 / 总销售额 * 100
,计算百分比。
- 计算T-shirt销售额占总销售额的百分比:
查询结果格式
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"sales_per_month" : {
"buckets" : [
{
"key_as_string" : "2024-01-01T00:00:00.000Z",
"key" : 1704067200000,
"doc_count" : 3,
"total_sales" : {
"value" : 95.48000144958496
},
"t-shirts" : {
"doc_count" : 2,
"sales" : {
"value" : 45.489999771118164
}
},
"t-shirt-percentage" : {
"value" : 47.64348458366713
}
},
{
"key_as_string" : "2024-02-01T00:00:00.000Z",
"key" : 1706745600000,
"doc_count" : 3,
"total_sales" : {
"value" : 120.97999954223633
},
"t-shirts" : {
"doc_count" : 2,
"sales" : {
"value" : 45.97999954223633
}
},
"t-shirt-percentage" : {
"value" : 38.00628179551602
}
}
]
}
}
}
这个结果表示:
- 2024年1月的总销售额为 ** 95.48**。
- 其中 45.48 元来自于 T-shirt。
- T-shirt 的销售占比为 ** 47.6%**。
示例计算:点击率 (CTR)
假设有个广告展示量和点击量的聚合,想计算每个广告的点击率:
{
"aggs": {
"ads": {
"terms": {
"field": "ad_id"
},
"aggs": {
"impressions": {
"sum": {
"field": "impression_count"
}
},
"clicks": {
"sum": {
"field": "click_count"
}
},
"ctr": {
"bucket_script": {
"buckets_path": {
"clicks": "clicks",
"impressions": "impressions"
},
"script": "params.clicks / params.impressions"
}
}
}
}
}
}
逻辑:
- 使用
terms
聚合按广告 ID 分组 - 分别计算广告的展示量 (
impressions
) 和点击量 (clicks
) - 使用
bucket_script
聚合计算 点击率(CTR) =点击量 / 展示量
6. 注意事项与限制
- 性能影响:由于 Bucket Script 在已有聚合结果上执行计算,处理大量桶时可能会导致性能下降。
- 路径依赖:
buckets_path
必须引用当前层级内或父层级的聚合结果,不能跨层级引用。 - 脚本限制:ElasticSearch 默认使用 Painless 脚本, 确保脚本逻辑高效,否则可能导致查询超时。
- 溢出处理:注意在脚本中处理除零异常或数据溢出。
7. 最佳实践
- 数据过滤:提前过滤无关数据,减少参与计算的桶数。
- 逐步聚合:将复杂计算分解为多个简单的管道聚合,以提高可读性和维护性。
- 性能调优:如果计算复杂,可以限制返回结果的桶数(例如通过
size
限制 top-N 结果)。