如何扁平化Elastic Search查询返回的聚合结果
问题:Elasticsearch聚合结果扁平化处理需求
我们基于Elasticsearch存储的应用日志生成报表,受技术栈限制需在外部系统做ETL转换(含系统术语转业务术语),目前通过调用Elasticsearch REST API查询后生成报表,无法使用SQL类接口。
当前使用的聚合查询如下:
{ "query" : { ### filtering criteria, not relevant to the question }, "aggs":{ "GroupBy": { "terms": {"field": "xxx"}, "aggs": { "CountDistinct": { "cardinality": {"field": "yyy"} } } } } }
返回结果结构如下:
{ "aggregations": { "GroupBy": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "GroupByValue0", "doc_count": 30000, "CountDistinct": { "value": 800 } }, { "key": "GroupByValue1", "doc_count": 20000, "CountDistinct": { "value": 200 } } ] } } }
实际查询嵌套更复杂,希望将聚合结果扁平化以简化后续流程,期望得到的结果格式如下:
{ "aggregations": { "GroupBy": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "GroupByValue0", "doc_count": 30000, "CountDistinct": 800 }, { "key": "GroupByValue1", "doc_count": 20000, "CountDistinct": 200 } ] } } }
请问能否通过Elasticsearch原生查询或聚合能力实现该需求?
回答
可以通过Elasticsearch的scripted_metric聚合实现结果扁平化,替代原有的cardinality聚合,直接在聚合阶段将值提取为扁平字段。
修改后的查询示例如下:
{ "query": { ### filtering criteria, not relevant to the question }, "aggs": { "GroupBy": { "terms": {"field": "xxx"}, "aggs": { "CountDistinct": { "scripted_metric": { "init_script": "state.values = new HashSet()", "map_script": "state.values.add(doc['yyy'].value)", "combine_script": "return state.values.size()", "reduce_script": "int total = 0; for (s in states) { total += s; } return total" } } } } } }
各脚本作用:
init_script:初始化一个HashSet用于存储去重后的字段值map_script:将每个文档的yyy字段值加入HashSetcombine_script:返回当前分片上HashSet的大小(即去重计数)reduce_script:汇总所有分片的计数结果,得到全局去重值
执行该查询后,返回的CountDistinct字段会直接是数值,而非嵌套的{ "value": 数值 }结构,完全符合你期望的扁平化格式。
注意:如果yyy是高基数字段,scripted_metric的内存占用会比原生cardinality聚合高(因为cardinality用HyperLogLog算法做近似计数);若对精度要求不高,也可以在cardinality聚合后通过bucket_script提取值,但写法相对繁琐,推荐优先使用scripted_metric实现需求。
内容的提问来源于stack exchange,提问作者alko
相关产品推荐
相关产品推荐

