$addFields 需配合 $cond 实现条件赋值,因其本身只能无差别添加字段;若需字段“有条件存在”,必须改用 $project + $$REMOVE。

$addFields 本身不支持直接“条件填充字段”,但配合 $cond 表达式就能实现——它不是开关式赋值,而是每个文档独立计算、按需写入。
为什么不能只用 $addFields 写死字段?
因为 $addFields 的每个键值对都会无差别添加到所有输出文档。如果你只想给 status: "active" 的用户加 priority 字段,而其他用户不加(或加不同值),就必须靠表达式动态控制值,而不是靠“是否出现该字段”来控制。
常见误解是以为 $addFields 能像 $project 那样用 $$REMOVE 条件排除字段——但它不能。它的行为是“全有或全覆写”,没有“跳过添加”这一选项。
$addFields + $cond 的标准写法
核心是把字段值设为一个 $cond 表达式,由条件分支决定最终值:
-
$cond必须是三元结构:{if: ..., then: ..., else: ...} -
then和else分支可以是任意合法表达式,包括null、字符串、数字、对象,甚至$$REMOVE(但注意:$$REMOVE在$addFields中无效,仅在$project中起作用) - 若想让某类文档“不出现该字段”,
$addFields做不到;必须换用$project+$$REMOVE
示例:给订单加 discountType 字段,金额 ≥ 1000 时为 "vip",否则为 "normal"
db.orders.aggregate([
{
$addFields: {
discountType: {
$cond: {
if: { $gte: ["$amount", 1000] },
then: "vip",
else: "normal"
}
}
}
}
])
想“完全跳过添加字段”?改用 $project + $$REMOVE
这是最容易踩坑的地方:很多人硬用 $addFields 想实现“条件存在性”,结果字段始终出现(哪怕值是 null),导致下游逻辑误判。
真正需要“字段有时存在、有时不存在”时,必须切到 $project:
-
$project允许对同一字段用表达式,且当表达式结果为$$REMOVE时,该字段从输出中彻底消失 - 你得显式列出所有要保留的原始字段,或用
$$ROOT展开再过滤,工作量略大 - 性能上无明显差异,但语义更精确
等效需求的 $project 写法:
db.orders.aggregate([
{
$project: {
_id: 1,
amount: 1,
status: 1,
// 其他必须保留的字段...
discountType: {
$cond: {
if: { $gte: ["$amount", 1000] },
then: "vip",
else: "$$REMOVE" // ← 这里才真正删掉字段
}
}
}
}
])
嵌套字段和数组里的条件填充
点号路径在 $addFields 中照常生效,$cond 也能嵌套使用:
- 给
user.profile添加profile.isVerified:用"profile.isVerified"作为字段名 - 给数组元素加字段(如每个
items元素加isExpensive):必须配合$map,不能只靠点号 -
$cond可以出现在$map的in:表达式里,实现逐元素判断
例如:为购物车每项标记是否高价
db.carts.aggregate([
{
$addFields: {
items: {
$map: {
input: "$items",
as: "item",
in: {
$mergeObjects: [
"$$item",
{
isExpensive: {
$cond: { if: { $gt: ["$$item.price", 50] }, then: true, else: false }
}
}
]
}
}
}
}
}
])
关键点始终是:字段是否存在,由阶段类型决定;字段值是什么,才由 $cond 决定。$addFields 管“值”,$project 管“存不存在”。混淆这两层,就会反复调试却得不到预期结构。


















