如何通过单条Elasticsearch查询实现考试数据去重多字段计数
答案
可以,整套筛选、去重、统计逻辑完全可以通过单条Elasticsearch查询实现,不需要拆分多次请求。
具体实现思路
你可以通过「过滤+terms分组聚合+top_hits子聚合+分组内条件计数」的组合完成所有需求,三步业务逻辑和ES能力的映射关系如下:
- 对应第一步条件筛选:不需要先拉全量记录再筛人,直接在聚合层做前置过滤,拿到所有存在
Physics = "Fail"记录的Roll no集合,自动排除所有物理成绩全及格的学生(也就是示例里的学号2),再反向匹配这些学号对应的全量考试记录即可。 - 对应第二步按学号去重:基于筛选出的学号集合,以
Roll no做terms分组,每组通过top_hits子聚合取1条任意记录(size设为1,不指定排序规则就满足“不限制保留考试场次”的要求),这一步返回的结果和给出的去重示例结构完全一致。 - 对应第三步分科目Pass人数统计:在每个
Roll no的分组内做过滤计数,统计对应科目值为Pass的数量时,自动忽略值为Not available的记录,最后把各分组的计数累加就是最终结果。
参考查询结构(简化版)
POST /exam_records/_search { "size": 0, "aggs": { "qualified_rollnos": { "terms": { "field": "Roll no", "size": 10000, "include": { "partition": 0, "num_partitions": 1 } }, "aggs": { "has_physics_fail": { "filter": { "term": { "Physics": "Fail" } } }, "dedup_record": { "top_hits": { "size": 1, "_source": ["Exam", "Name", "Roll no", "English", "Hindi", "Physics", "Maths"] } }, "bucket_filter": { "bucket_selector": { "buckets_path": { "failCount": "has_physics_fail>_count" }, "script": "params.failCount > 0" } }, "english_pass": { "filter": { "bool": { "must": [{"term": {"English": "Pass"}}], "must_not": [{"term": {"English": "Not available"}}] } } }, "hindi_pass": { "filter": { "bool": { "must": [{"term": {"Hindi": "Pass"}}], "must_not": [{"term": {"Hindi": "Not available"}}] } } }, "physics_pass": { "filter": { "bool": { "must": [{"term": {"Physics": "Pass"}}], "must_not": [{"term": {"Physics": "Not available"}}] } } }, "maths_pass": { "filter": { "bool": { "must": [{"term": {"Maths": "Pass"}}], "must_not": [{"term": {"Maths": "Not available"}}] } } } } } } }
返回结果里,dedup_record就是去重后的单条学生记录,各*_pass子聚合的doc_count累加值就是对应科目的及格人数,和给出的预期结果完全匹配。
补充说明:因为当前去重规则不限制保留哪场考试的记录,只要保证每个学号仅取1条参与统计,结果就符合业务要求。如果后续需要固定取某场考试(比如最晚的mid-term 3),只需要在
top_hits里加对应排序规则即可,统计逻辑不需要改动。
内容的提问来源于stack exchange,提问作者user3401007
相关产品推荐
相关产品推荐

