MongoDB 4.4中如何在$group操作中取每组前5条数据求和?
问题描述
生产服务器使用MongoDB 4.4,现有如下聚合查询可正常运行:
db.step_tournaments_results.aggregate([ { "$match": { "tournament_id": "6377f2f96174982ef89c48d2" } }, { "$sort": { "total_points": -1, "time_spent": 1 } }, { $group: { _id: "$club_name", 'total_points': { $sum: "$total_points"}, 'time_spent': { $sum: "$time_spent"} }, }, ])
当前$group操作会对每组所有数据的total_points和time_spent进行求和,但需求是仅取每组前5条数据计算总和,请问如何实现?
实现方案
可以通过两种方式实现需求,以下是具体方案:
方案一:分组存数+切片求和
先按俱乐部收集排序后的所有数据,截取前5条后再展开求和,代码如下:
db.step_tournaments_results.aggregate([ // 筛选目标赛事数据 { "$match": { "tournament_id": "6377f2f96174982ef89c48d2" } }, // 按积分降序、耗时升序排序 { "$sort": { "total_points": -1, "time_spent": 1 } }, // 按俱乐部分组,收集所有排序后文档到数组 { "$group": { "_id": "$club_name", "top5_docs": { "$push": "$$ROOT" }, } }, // 截取数组前5条,保留每组排名靠前的数据 { "$addFields": { "top5_docs": { "$slice": ["$top5_docs", 5] } } }, // 将数组拆分为单个文档,方便后续求和 { "$unwind": "$top5_docs" }, // 重新分组,计算前5条数据的总和 { "$group": { "_id": "$_id", "total_points": { "$sum": "$top5_docs.total_points" }, "time_spent": { "$sum": "$top5_docs.time_spent" } } } ])
关键步骤说明
$push: "$$ROOT":把每个俱乐部的所有排序后文档存入数组$slice:直接截取数组前5个元素,过滤掉每组排名靠后的数据$unwind:将数组拆分为单条文档,让后续的$group能正确求和
方案二:窗口函数过滤求和
利用MongoDB 4.2+支持的窗口函数给每条数据标记组内排名,再过滤前5条后求和,代码更简洁:
db.step_tournaments_results.aggregate([ { "$match": { "tournament_id": "6377f2f96174982ef89c48d2" } }, // 按俱乐部分组,组内排序并添加排名字段 { "$setWindowFields": { "partitionBy": "$club_name", "sortBy": { "total_points": -1, "time_spent": 1 }, "output": { "rank": { "$rank": {} } } } }, // 只保留每组排名前5的数据 { "$match": { "rank": { "$lte": 5 } } }, // 分组计算总和 { "$group": { "_id": "$club_name", "total_points": { "$sum": "$total_points" }, "time_spent": { "$sum": "$time_spent" } } } ])
关键步骤说明
$setWindowFields:按club_name划分窗口,在窗口内按规则排序后给每条数据分配排名$match过滤:直接筛选出每组排名≤5的数据,避免了收集全组数据的开销- 最终
$group直接对过滤后的数据求和,逻辑更直观
两种方案都能满足需求:如果每组数据量极大,窗口函数方案性能更优;数据量适中时,两种方案差异不大。
内容的提问来源于stack exchange,提问作者socm_
相关产品推荐
相关产品推荐

