$setWindowFields 的 rank 计算返回 null 是因未显式指定 sortBy,且该字段必须存在、可比较;sortBy 必须为对象(如 {score: -1}),不支持嵌套字段或缺失/空值直接排序。

为什么 $setWindowFields 的 rank 计算总是返回 null?
因为 $setWindowFields 不会自动推导排序依据——你必须显式指定 sortBy,且字段必须存在、类型可比较。常见错误是直接套用 SQL 思维,漏掉 sortBy 或对未索引/含 null 的字段排序。
实操建议:
-
sortBy必须是对象,如{score: -1},不能是字符串或数组 - 若排序字段有
null或缺失,MongoDB 默认把它排在最前(升序)或最后(降序),可能造成排名跳变 - 确保该字段在聚合管道上游已存在,比如在
$addFields后再用$setWindowFields - 不支持对嵌套字段(如
"meta.rating")直接排序,需先用$set提取到顶层
rank、denseRank、documentNumber 三者怎么选?
它们都通过 $rank、$denseRank、$documentNumber 表达式实现,但语义完全不同:
-
$rank:相同值并列,跳过后续名次(如 [100,100,90] → [1,1,3]) -
$denseRank:相同值并列,不跳名次(如 [100,100,90] → [1,1,2]) -
$documentNumber:纯按输入顺序编号,无视值是否相等(即“行号”)
示例(按 score 降序排名):
{
$setWindowFields: {
sortBy: { score: -1 },
output: {
rank: { $rank: {} },
denseRank: { $denseRank: {} },
rowId: { $documentNumber: {} }
}
}
}
如何按分组计算组内排名?
必须配合 partitionBy 使用,否则就是全集合统一排名。关键点在于:partitionBy 的值必须是标量(不能是数组或对象),且通常来自已有字段。
- 错误写法:
partitionBy: "$tags"(若tags是数组,会报错 “Partition key must be a scalar”) - 正确做法:先用
$set提取分组键,例如{ $set: { category: { $arrayElemAt: ["$tags", 0] } } },再partitionBy: "$category" - 注意:
partitionBy和sortBy可共存,MongoDB 先分组,组内再排序计排名 - 性能提示:如果分组键基数很高(如上百万个不同 user_id),窗口运算开销显著上升,考虑是否真需实时计算
为什么加了 $setWindowFields 后查询变慢甚至超时?
因为 $setWindowFields 在内存中维护窗口状态,数据量大时极易触发 Exceeded memory limit for $group, but didn't allow external sort 类错误,尤其当没设 allowDiskUse: true 或缺少支撑索引时。
- 必须确保
sortBy字段有单字段索引(升序/降序均可),否则无法高效排序 - 若使用
partitionBy,理想索引是复合索引:{ partitionField: 1, sortByField: -1 } - 聚合命令需显式传
{ allowDiskUse: true },否则 100MB 内存限制会立刻卡住 - 线上慎用:千万级文档的全量窗口排名,应优先考虑预计算 + 缓存,而非每次查库实时算
真正难处理的是排序字段动态变化(比如用户可选按销量/评分/时间排),这种场景几乎没法靠单一索引覆盖,得结合应用层做策略分流。

















