必须用聚合管道语法(中括号数组)才能在$set中正确引用字段,如{ $set: { backupTitle: "$title" } }需写为[ { $set: { backupTitle: "$title" } } ],否则"$title"被当字面量字符串处理。

用 $set 和 $ 引用当前文档字段时,必须配合聚合表达式
直接写 { $set: { newField: "$oldField" } } 是无效的——MongoDB 会把它当字面量字符串处理,而不是字段引用。真正起作用的是聚合管道语法,即把 $set 当作聚合阶段使用,内部用 $ 表达式访问字段值。
常见错误现象:newField 的值变成字符串 "$oldField",而不是 oldField 的实际内容。
- 必须在
updateMany()或updateOne()中启用聚合管道模式(即第二个参数传入数组) - 不能在传统更新操作(如
{ $set: { ... } }单对象形式)里混用$字段引用 - 字段名含点号(如
"user.name")需用$getField或嵌套引用"$user.name",但前提是路径合法且存在
单字段复制:最简写法是 $set + 聚合表达式数组
例如把 title 复制到 backupTitle:
db.posts.updateMany(
{},
[ { $set: { backupTitle: "$title" } } ]
)
注意括号里的中括号——这是关键。它告诉 MongoDB 这是一个聚合管道,允许使用变量式引用。
- 空查询条件
{}表示全量更新,生产环境务必加筛选条件(如{ title: { $exists: true } }) - 如果目标字段已存在,会被直接覆盖;不存在则新建
- 若
title为null或缺失,backupTitle也会被设为null或缺失(取决于是否启用upsert)
多字段赋值或带逻辑处理:在同一个 $set 阶段里组合表达式
比如同时复制 name 到 alias,并把 createdAt 转成字符串存进 createdStr:
db.users.updateMany(
{ name: { $exists: true } },
[
{
$set: {
alias: "$name",
createdStr: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } }
}
}
]
)
所有表达式都在一个 $set 内完成,避免多次遍历文档,性能更好。
- 每个表达式独立求值,互不影响
- 像
$dateToString、$concat、$ifNull这类操作必须放在聚合表达式上下文中,不能出现在普通$set对象里 - 若某字段可能不存在,用
$ifNull: ["$missingField", "default"]避免整个更新失败
嵌套字段和数组字段的引用要小心路径有效性
比如想把 profile.firstName 赋给 displayName,直接写 "$profile.firstName" 可以工作;但如果 profile 是数组,就得用 $arrayElemAt 或 $first 提取元素。
错误示例(导致 displayName 为 null):{ $set: { displayName: "$profile.firstName" } } —— 当 profile 是空数组或缺失时,该表达式返回 null。
- 安全做法:先用
$cond或$ifNull做兜底,例如$ifNull: ["$profile.firstName", "$username"] - 数组首项提取:用
{ $arrayElemAt: ["$tags", 0] },而非"$tags.0"(后者非法) - 更新前建议先用
find()抽样检查字段结构,尤其在 schema 不严格的情况下
findOne() 看一眼目标文档结构,字段是否存在、类型是否一致、嵌套层级是否预期——这些细节不验证,$set 管道跑起来不会报错,但结果往往和你想的不一样。


















