$sum配合$group只能计算分组总和,无法实现逐行累计;累计求和必须用$setwindowfields(mongodb 4.4+),它支持按排序顺序逐行叠加并保留每条记录的运行总和,功能等效于sql的sum() over (order by ...)。

用 $sum 配合 $group 只能算总和,不是累计求和
很多人一看到“求和”就直接套 $sum,结果发现它只返回一个最终值,没法得到每条记录对应的累计值。累计求和本质是“逐行叠加”,必须保留原始文档顺序,并在每一步带上前面所有值的和。MongoDB 4.4+ 提供了 $setWindowFields,这才是正解——它专为这类窗口计算设计,类似 SQL 的 SUM() OVER (ORDER BY ...)。
$setWindowFields 是唯一可靠方式(4.4+)
低版本 MongoDB($setWindowFields,支持按指定字段排序后,在滑动窗口内做累加。关键点:
- 必须搭配
partitionBy(可选)和sortBy使用,否则顺序不确定,累计结果错乱 -
output里用$sum时,窗口范围得设成{ "documents": [ "unbounded", "current" ] },表示从第一行加到当前行 - 如果集合没索引对应
sortBy字段,聚合会变慢,建议提前建索引
示例:按时间顺序计算每日销售额累计值
[
{
"$setWindowFields": {
"sortBy": { "date": 1 },
"output": {
"cumulativeSales": {
"$sum": "$sales",
"window": { "documents": [ "unbounded", "current" ] }
}
}
}
}
]
常见错误:误用 $reduce 或 $map 模拟累计
有人试图用 $reduce 把整个数组传进去再累加,这其实是在单个文档内操作,无法跨文档累计;还有人把全部数据 $push 成数组再遍历,既吃内存又破坏流式处理能力。这些写法:
- 只适用于小数据集(几万条以内),数据量大时内存溢出或超时
- 无法利用索引加速排序,
$sort阶段变成瓶颈 - 结果不可分页,因为累计值依赖全量数据顺序
真正需要累计求和的场景(如财务流水、用户行为路径),应该直接上 $setWindowFields,别绕路。
兼容性提醒:3.6/4.0 版本怎么办?
如果你卡在旧版本,没有 $setWindowFields,就不要强行在聚合里实现累计逻辑。更务实的做法是:
- 在应用层查出已按序排列的数据(带
.sort({ date: 1 })),再用 for 循环累加赋值 - 用变更流或定时任务,把累计值预计算并存到新字段里,查询时直接读
- 升级 MongoDB —— 这不是建议,是必要项;4.4 已是 2020 年发布的 LTS 版本,继续用 3.6 会持续遇到类似限制
累计求和看着简单,但核心在于“顺序敏感 + 增量依赖”,MongoDB 的设计哲学是把这类计算交给专用窗口操作符,而不是让开发者拼凑 workaround。用错方法,后期数据对不上,排查起来比重写还费劲。











