是什么
$replaceWith 用表达式求出的文档整体替换输出的根文档,旧名写法是 $replaceRoot 配 newRoot。它最常见的用法是把 $unwind 或 $arrayElemAt 取出的子文档提升为顶层结构,让后续阶段直接按子文档的字段路径书写;也可以用 $mergeObjects 把新算出的字段与原文档拼在一起再当作根。
基本信息
| 新写法 | { $replaceWith: <表达式> }(MongoDB 4.2+) |
|---|---|
| 旧写法 | { $replaceRoot: { newRoot: <表达式> } }(3.4+) |
| 典型组合 | $unwind、$arrayElemAt、$mergeObjects、$$ROOT |
| 效果 | 替换后字段路径随之改变,后续阶段按新结构写 |
| 注意 | 表达式求值为数组或字段缺失时可能出错,先过滤不满足条件的文档 |
典型使用场景
- 展开数组后按元素统计:$unwind 明细数组再重塑,管道里就不用写「数组.字段」这种长路径
- 保留原文档并追加字段:用 $mergeObjects 把新字段与 $$ROOT 合并,既有新信息又不丢原字段
- 取数组首元素:用 $arrayElemAt 取出主联系人、主地址等元素作为根
- 嵌套结构拍平:把深层子文档提升一层,简化后续匹配与排序
- 多集合合并输出统一结构:不同来源重塑成同一套字段后统一写入
重塑文档结构
重塑文档结构
db.orders.aggregate([
{ $match: { "items.0": { $exists: true } } }, # 先排除明细为空、重塑会出问题的文档
{ $unwind: "$items" }, # 展开明细数组
{ $replaceWith: "$items" }, # 明细子文档成为新的根
{ $sort: { qty: -1 } } # 之后直接按明细字段排序
])
db.users.aggregate([ # 保留原字段并追加计算字段
{ $replaceWith: { $mergeObjects: [ { nameUpper: { $toUpper: "$name" } }, "$$ROOT" ] } },
{ $limit: 5 }
])
db.users.aggregate([ # 取数组首元素作为根
{ $match: { contacts: { $ne: [] } } },
{ $replaceWith: { $arrayElemAt: ["$contacts", 0] } }
])相关写法对照
| 写法 | 作用 | 版本 |
|---|---|---|
| $replaceWith: expr | 表达式结果整体作为新根 | 4.2+ |
| $replaceRoot: { newRoot: expr } | 旧写法,效果相同 | 3.4+ |
| $mergeObjects: [新增字段, "$$ROOT"] | 保留原文档并追加字段 | 3.6+ |
| $arrayElemAt: ["$arr", 0] | 取数组首个元素作为新根 | 3.2+ |
重塑之后所有的字段路径都变了:前面写的 $match 用旧路径,后面的阶段要用新路径,管道里混用会得到空结果或报错,写完最好先用 $limit 小样验证。
表达式求值为数组、或目标字段缺失的类型不满足要求时会出错,而错误是逐文档发生的:先用 $match 把不合格文档挡在前面,比事后排查半截失败的结果成本低得多。