$merge必须作为聚合管道最后一个阶段,用于将结果写入集合;它不是数据转换操作,而是写入动作,要求所有计算在之前完成,且on字段必须存在于每条输出文档中。

$merge 必须作为管道最后一个阶段
聚合管道里,$merge 只能出现在末尾,否则会直接报错 "unknown stage name: $merge"。它不是数据转换阶段,而是写入动作,MongoDB 要求所有计算必须在它之前完成。
常见错误是把 $merge 放在 $sort 或 $limit 后面还加了其他阶段,比如:
{$merge: {into: "stats", on: "_id"}}, {$project: {_id: 1}}
这会失败。正确顺序是:所有 $group、$lookup、$set 等处理完 → 最后一个阶段必须是 $merge。
on 字段必须存在于每条输出文档中
$merge 不会帮你补字段,如果管道输出的文档里没有 on 指定的键(比如设了 on: "userId",但中间漏了 $set: {userId: "$user._id"}),那这条文档就静默丢弃——不报错、不写入、也不提示。
- 用
db.collection.aggregate([...]).toArray()先跑一遍,检查输出结构是否含on字段 - 对
$unwind后可能丢失原始_id的场景,显式加{$set: {originalId: "$_id"}}并设on: "originalId" - 复合键匹配(如
["customerId", "date"])时,确保两个字段都存在且类型一致(字符串不能和数字比)
whenMatched 和 whenNotMatched 的取值要按需选
whenMatched 控制已有文档怎么处理:"replace" 是整条覆盖(危险,会丢掉你手动加的字段),"merge" 只合并同名字段(推荐),"keepExisting" 完全跳过更新。
whenNotMatched 控制新文档行为:"insert" 是安全默认;"discard" 会直接扔掉不匹配的数据,容易导致结果缺失,除非你明确想过滤掉某些记录。
示例中常见误配:
{$merge: {into: "daily_summary", on: "dateKey", whenMatched: "replace", whenNotMatched: "discard"}}
这相当于“只更新已有的日期,新日期一律不要”——多数报表场景下这是错的。
跨数据库写入必须用 {db: "...", coll: "..."} 语法
不能在 from 或 into 里直接写 "otherdb.collection",MongoDB 会当成一个集合名去当前库找。跨库写入必须用对象形式:
{$merge: {into: {db: "reporting", coll: "monthly_sales"}, on: "_id"}}
注意两点:
- 目标数据库必须已存在(分片集群下尤其严格,不会自动创建)
- 源集合跨库关联要用
$lookup+db.getSiblingDB()配合临时集合,不能靠$merge直接跨库拉取
最容易被忽略的是权限——目标库的写权限必须显式授予,哪怕你当前连接的是 admin 库,也不能自动继承对 reporting 库的写权限。


















