累计求和必须用 $setwindowfields(5.0+)或手动模拟,$sum 仅支持全局加总;$setwindowfields 需显式 sortby 和对应索引,窗口范围设为 ["unbounded", "current"];旧版本需 $group+$push+$reduce,易内存溢出;累计结果不可直接分页,否则值断裂。

用 $sum 做累计求和?别直接套,它只做全局加总
直接在 $group 阶段用 $sum 只能算出一个总数,不是按顺序逐条累加。要实现“第1条值、前2条和、前3条和……”这种累计效果,必须借助 $setWindowFields(MongoDB 5.0+)或手动模拟窗口逻辑(旧版本)。
$setWindowFields 是最简方案,但有版本和索引硬要求
它专为这类计算设计,但容易因排序缺失或缺少索引报错:FieldPath field references must be prefixed with a valid window field name 或 Sort order must be specified。
- 必须显式指定
sortBy字段,且该字段需有索引(否则聚合可能失败或极慢) - 累计求和写法固定:
{ $sum: "$amount" }放在output的sumAmount: { $sum: "$amount" }里 - 窗口范围必须设为
{ documents: [ "unbounded", "current" ] },表示从开头到当前行
db.orders.aggregate([
{ $sort: { createdAt: 1 } },
{
$setWindowFields: {
sortBy: { createdAt: 1 },
output: {
runningTotal: { $sum: "$amount" }
}
}
}
])
MongoDB 4.4 及更早版本只能手写累计逻辑
没有窗口函数,就得靠 $group + $push 把数据攒成数组,再用 $reduce 逐个累加——但数据量一大就内存溢出,且无法流式处理。
- 先
$sort再$group把所有文档聚合成一个大数组,_id: null是常见写法 -
$reduce的初始值必须是[],每次把新累加值$concat进数组 - 最后用
$unwind拆开,再$project补回原始字段,顺序极易错乱
db.orders.aggregate([
{ $sort: { createdAt: 1 } },
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
runningTotals: {
$reduce: {
input: "$docs",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[{ sum: { $add: [{ $sum: "$$value.sum" }, "$$this.amount"] } }]
]
}
}
}
}
}
])
累计求和结果不能直接用于分页,这是最容易忽略的坑
$setWindowFields 输出的累计字段不改变文档数量,但如果你在它之后加 $skip/$limit,累计值会断掉——比如跳过前10条后,第11条的 runningTotal 是从第11条开始算的,不是全局累计。
- 真要分页显示累计值,必须先完成全部累计计算,再在外层应用分页
- 大数据集慎用:累计过程无法利用索引下推,全量扫描不可避免
- 如果只是需要“截至某时间点的总和”,用
$sum+$match更高效,别硬套累计逻辑











