$subtract计算字段差值时顺序不可颠倒,必须为[$减数, $被减数],写反会导致符号错误;支持字段路径、数值或嵌套表达式;null或缺失字段返回null;时间字段需先用$tolong转毫秒再运算。

用 $subtract 计算两个字段差值时字段顺序不能颠倒
$subtract 接收一个长度为 2 的数组,格式是 [$减数, $被减数] —— 注意:**第一个是减数,第二个是被减数**,不是直觉上的“被减数减减数”。写反会导致结果符号相反,且不会报错,容易漏检。
常见错误场景:想算 price - cost 却写成 {$subtract: ["$cost", "$price"]},结果得到负值。
- 正确写法:
{$subtract: ["$price", "$cost"]} - 支持字段路径(
"$field")、数值字面量(100)或嵌套表达式(如{$add: ["$a", "$b"]}) - 若任一操作数为
null或缺失,整个结果为null,不抛异常
处理可能缺失或非数字字段的容错写法
直接用 $subtract 遇到字符串、null、缺失字段会静默返回 null,但有时你需要明确过滤或默认值。
推荐组合 $cond + $isNumber 做前置校验:
{
$subtract: [
{ $cond: { if: { $isNumber: "$price" }, then: "$price", else: 0 } },
{ $cond: { if: { $isNumber: "$cost" }, then: "$cost", else: 0 } }
]
}
- 避免因字段类型不对导致整条文档在后续阶段被跳过(比如
$group中null不参与累加) - 若需保留原始缺失状态,可把
else: null,但注意下游是否能处理null -
$convert(4.0+)也可强制转数字,但失败时默认返回null,不如$isNumber显式可控
在 $project 和 $addFields 中使用差异字段的区别
两者都能添加计算字段,但语义和性能影响不同:
-
$project:只保留显式列出的字段(含新字段),其他字段被丢弃 —— 如果你只需要差值和少量字段,用它更省传输带宽 -
$addFields:保留所有原有字段,只新增 —— 更安全,尤其在 pipeline 后续还要用原字段时 - 性能上无本质差异,但
$project过早丢弃字段可能让后续$match无法利用索引(如果过滤条件依赖被丢弃的字段)
聚合中对时间字段做差值要先转为毫秒数
MongoDB 时间字段(Date 类型)不能直接传给 $subtract。必须先用 $toDate(确保类型)再用 $toLong 转为毫秒时间戳。
例如计算两个时间间隔(单位:秒):
{
$divide: [
{ $subtract: [
{ $toLong: "$endTime" },
{ $toLong: "$startTime" }
]
},
1000
]
}
-
$toDate可选但推荐:防止字符串格式时间(如"2024-01-01")被误当字符串处理 - 注意时区:MongoDB 存储为 UTC,
$toLong返回的是 UTC 毫秒数,无需额外时区转换 - 别用
$dateDiff(5.0+)替代?可以,但它返回的是带单位的结构体,不如$subtract+$toLong直接得数值灵活
字段类型不一致、顺序写反、时间没转毫秒——这三个点实际调试时占了八成以上差值计算问题。











