$sum在$setWindowFields中计算移动总和需配合partitionBy、sortBy及window(documents或range),仅适用于MongoDB 5.0+原生环境,Azure DocumentDB不支持;$group无法替代,因其丢失原始行。

$sum 在 $setWindowFields 中的移动总和写法
MongoDB 从 5.0 开始支持窗口函数,$sum 可以作为累加器在 $setWindowFields 阶段计算移动总和(running sum),但必须配合 partitionBy 和 sortBy 显式声明顺序,否则结果无意义或报错。
常见错误现象:unrecognized field '$sum'(未在 $setWindowFields 内正确嵌套)、sortBy is required(漏写排序)、移动窗口范围不生效(误用 documents 而非 range 或反之)。
-
partitionBy不是必需的,但如果省略,整个集合视为一个分区;实际业务中几乎总是需要按某字段(如"user_id"、"product_id")分区,否则跨用户/商品混算 -
sortBy必须是单个字段,且类型需支持比较(如Date、Number),不能是嵌套对象或数组 - 移动窗口范围通过
output: { runningTotal: { $sum: "$amount" } }+window: { documents: [-2, 0] }实现:表示“当前行及前两行”的累计和 - 若用
range(如{ range: [-7, 0], unit: "day" }),则要求sortBy字段是Date类型,且单位必须匹配
为什么 $group + $sum 不能替代移动总和
$group 是静态分组聚合,输出每组一个文档;而移动总和要求**每个原始文档都保留,并附加一个基于窗口的新字段**。强行用 $group 模拟会丢失明细行、无法回溯原始数据上下文。
典型误用场景:
- 想查“每个订单的累计销售额”,却写成
{$group: {_id: "$order_id", total: {$sum: "$amount"}}}→ 这只是单笔订单内各子项求和,不是跨订单移动累计 - 试图用
$push收集所有历史值再$reduce计算,性能极差且内存易爆(尤其大数据集) - 在应用层循环查、累加——网络往返多、状态难维护、并发下不一致
真正需要移动总和时,$setWindowFields 是唯一合理路径。
documents vs range:两种窗口边界的取舍
documents 按物理行偏移定义窗口(如 [-3, 0] 表示前3行+当前行),range 按 sortBy 字段值跨度定义(如 {range: [-30, 0], unit: "second"})。两者互斥,选错会导致结果完全偏离预期。
- 时间序列场景(如每秒指标滚动求和)优先用
range,前提是sortBy是Date且索引已建好;否则可能因精度丢失(毫秒截断)或匹配不到 - 序号明确的流水(如带自增
seq字段的交易日志)适合documents,稳定、高效、无需考虑时间对齐问题 -
documents: [0, 0]等价于原地复制字段,documents: [0, 1]表示当前行+下一行之和——注意负数在左、正数在右是固定约定 - 使用
range时,若sortBy字段有重复值,MongoDB 会把所有同值行视为同一“位置”,导致窗口意外扩大
容易被忽略的兼容性与性能点
移动总和看似简单,但上线前必须确认三点:
- Azure DocumentDB(即 MongoDB 兼容模式)**不支持
$setWindowFields** —— 官方文档明确标注$sum在该服务中仅适用于$group和$project,尝试用窗口会直接报错Unrecognized pipeline stage name: '$setWindowFields' - 即使在原生 MongoDB 5.0+,
$setWindowFields无法利用大部分现有索引加速;sortBy字段必须有索引,否则聚合将全表扫描 - 窗口大小无硬限制,但
documents: [-1000, 0]在百万级集合上可能触发内存限制(maxMemoryUsageMB),需配合allowDiskUse: true - 如果源字段含非数值(如
null、string、missing),$sum自动跳过——这虽是设计行为,但若业务要求报错而非静默忽略,得前置用$cond校验


















