如何在ElasticSearch中聚合两个数组关联项的平均值
解决ElasticSearch跨文档数组配对元素的平均值聚合问题
核心思路
你的问题本质是要解决两个数组元素一一对应关联的聚合问题——ES默认会扁平化数组,直接聚合会导致runners和runners_times的元素错位。下面提供两种可行方案,优先推荐修改数据结构(方案一),这是最稳定高效的方式。
方案一:修改数据结构为嵌套对象数组(最优解)
把原有的两个独立数组字段,合并成一个嵌套的对象数组字段(比如runner_times),每个对象包含name和time两个属性,从根本上保证关联关系不丢失。
1. 创建新的索引映射
PUT /runner_stats { "mappings": { "properties": { "runner_times": { "type": "nested", "properties": { "name": {"type": "keyword"}, "time": {"type": "integer"} } } } } }
2. 重新导入数据
将原有文档转换为嵌套结构,比如原文档1变为:
{ "runner_times": [ {"name": "person_a", "time": 100}, {"name": "person_b", "time": 120} ] }
3. 执行聚合查询
使用nested聚合+terms聚合+avg聚合,直接得到每个跑步者的平均用时:
GET /runner_stats/_search { "size": 0, "aggs": { "nested_runner_times": { "nested": { "path": "runner_times" }, "aggs": { "group_by_runner": { "terms": { "field": "runner_times.name" }, "aggs": { "avg_run_time": { "avg": { "field": "runner_times.time" } } } } } } } }
查询结果会直接返回你需要的数值:person_a:95、person_b:110、person_c:120。
方案二:使用脚本字段动态配对(无需修改数据)
如果无法修改现有数据结构,可以通过Painless脚本在查询时动态将runners和runners_times的对应元素组合成键值对,再进行聚合。
聚合查询DSL
GET /your_index/_search { "size": 0, "runtime_mappings": { "runner_time_pair": { "type": "keyword", "script": """ def runners = doc['runners.keyword']; def times = doc['runners_times']; if (runners.size() != times.size()) { return; } for (int i = 0; i < runners.size(); i++) { emit(runners[i] + '|' + times[i]); } """ } }, "aggs": { "group_by_runner": { "terms": { "script": { "source": "doc['runner_time_pair'].value.split('\\|')[0]" } }, "aggs": { "avg_time": { "avg": { "script": { "source": "Integer.parseInt(doc['runner_time_pair'].value.split('\\|')[1])" } } } } } } }
这个脚本先将每个跑步者和对应时间拼接成person_a|100格式的字符串,再通过聚合拆分计算平均值。注意这种方式性能低于方案一,数据量大时不推荐。
构建柱状图仪表盘(以Kibana为例)
拿到聚合结果后,在Kibana中构建柱状图的步骤:
- 进入Visualize Library,创建新的Vertical Bar可视化
- 选择对应的索引模式
- 在Buckets的X轴选择Terms聚合,字段选跑步者名称(方案一选
runner_times.name,方案二选脚本生成的runner字段) - 在Metrics处选择Average,字段选对应的时间字段(方案一选
runner_times.time,方案二选脚本提取的时间值) - 调整图表样式后,保存到仪表盘即可
内容的提问来源于stack exchange,提问作者IgorStan
相关产品推荐
相关产品推荐

