MongoDB 5.0 不支持窗口函数,该功能自5.2起作为实验性特性引入、5.3正式GA;5.0中需用$sort+$group+$map+$slice+$reduce模拟移动平均,或升级版本后使用$setWindowFields。

窗口函数在 MongoDB 5.0 中是否可用?
MongoDB 5.0 **不支持**窗口函数。官方文档明确标注:窗口函数(如 $sum 配合 partitionBy、sortBy、range 等)是从 **MongoDB 5.2 开始引入**的实验性功能,并在 5.3 正式 GA。若你当前使用的是 5.0 版本,直接在 $setWindowFields 或聚合阶段中调用 $avg 的窗口变体,会触发错误:unrecognized pipeline stage name: '$setWindowFields'。
这意味着——别试了,语法解析器根本认不出这个阶段。
如何在 MongoDB 5.0 聚合中模拟移动平均?
没有 $setWindowFields,就得靠组合已有操作符“硬凑”出等效逻辑。核心思路是:对每个文档,用 $map + $slice + $reduce 在其前 N 条(按时间/序号排序后)数据上做局部计算。
前提条件:
- 集合中必须有可排序字段(如
timestamp或递增seq),否则“移动”无意义 - 数据量不能太大(单次聚合内存限制默认 100MB),否则
$lookup自关联或$facet展开易 OOM - 需先用
$sort+$group构建有序数组(这是关键中间步骤)
典型做法(以 3 期移动平均为例):
[
{ $sort: { timestamp: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{
$set: {
smoothed: {
$map: {
input: { $range: [0, { $size: "$data" }] },
as: "i",
in: {
$let: {
vars: {
window: {
$slice: [
"$data",
{ $subtract: ["$$i", 1] },
3
]
}
},
in: {
$cond: [
{ $lt: ["$$i", 2] },
null,
{ $avg: { $map: { input: "$$window", as: "w", in: "$$w.value" } } }
]
}
}
}
}
}
}
}
]
注意点:
-
$slice第二个参数是起始索引,第三个是长度;这里用{ $subtract: ["$$i", 1] }实现“从当前往前取 2 个+自身”,即中心对齐的 3 点窗口 - 开头两期无法构成完整窗口,返回
null是合理行为,避免插值污染 - 该写法把整个集合加载进内存,仅适用于万级以下文档;超量请改用应用层分批或升级 MongoDB
为什么不用 $lookup 自关联模拟窗口?
有人尝试用 $lookup 对每个文档查“时间戳落在 [t-2, t] 区间内”的其他文档,再 $avg。这在逻辑上可行,但实际踩坑严重:
常见问题:
- 自关联
$lookup在 5.0 不支持 pipeline 中引用外层变量(如"$$this.timestamp"),必须配合$expr+$let,语法极绕且性能差 - 即使写出,每次查都要全表扫描时间范围,O(n²) 复杂度,10k 文档可能耗时数秒以上
- 结果数组无序,还需额外
$sort,进一步拖慢
结论:能不用就不用。除非数据极度稀疏、窗口极小、且已建好复合索引({ timestamp: 1, _id: 1 }),否则纯属自找麻烦。
升级到 5.2+ 后怎么写干净的移动平均?
确认版本后(运行 db.version()),可直接用 $setWindowFields:
[
{ $sort: { timestamp: 1 } },
{
$setWindowFields: {
sortBy: { timestamp: 1 },
output: {
ma3: {
$avg: "$value",
window: { documents: [-2, 0] }
}
}
}
}
]
要点:
-
documents: [-2, 0]表示“当前行及前两行”,比 SQL 的ROWS BETWEEN 2 PRECEDING AND CURRENT ROW更直白 - 无需手动
$group成数组,内存占用低,支持千万级数据流式处理 - 若需按设备分组计算(如每台传感器独立移动平均),加
partitionBy: "$device_id"即可
真正容易被忽略的是:窗口函数依赖严格排序,$sort 必须在 $setWindowFields 前且不可被后续阶段打乱;一旦中间插入 $project 删除了排序字段,整个窗口就失效了。











