Oracle MERGE必须显式使用WHEN NOT MATCHED BY SOURCE THEN DELETE才能删除源中不存在的目标行,该子句仅支持DELETE且不允WHERE;ON条件须完整覆盖主键,否则会导致误删或报错。

WHEN NOT MATCHED BY SOURCE 必须显式写出
Oracle 的 MERGE 本身不支持“源表没这条,目标表就删掉”这种逻辑的默认行为。你不能只写 WHEN NOT MATCHED THEN ... 就指望它处理反向缺失——那个分支只管“源有、目标无”的插入场景。WHEN NOT MATCHED BY SOURCE 是独立且必须显式声明的子句,漏写就等于忽略该逻辑。
常见错误是误以为 ON 条件反向成立就能触发删除,结果执行完发现目标表里多出一堆“残留旧数据”。真正起作用的是这个明确命名的分支:
-
WHEN MATCHED THEN UPDATE ...:源和目标都存在,更新目标 -
WHEN NOT MATCHED THEN INSERT ...:源有、目标无,插入 -
WHEN NOT MATCHED BY SOURCE THEN DELETE:源无、目标有,删除(注意:只能跟DELETE,不能跟UPDATE或INSERT)
DELETE 只能放在 WHEN MATCHED 或 WHEN NOT MATCHED BY SOURCE 后面
Oracle 允许在 WHEN MATCHED THEN 后面接 DELETE WHERE ...,但前提是这个 DELETE 必须嵌套在 UPDATE 的同一分支中,且 WHERE 条件必须严格限定在已匹配行范围内——否则会报错 ORA-38104: Columns referenced in the ON Clause cannot be updated in the UPDATE clause 或更常见的 ORA-38102: Invalid column reference in DELETE WHERE clause。
更稳妥、也更符合“源不存在则删”本意的做法,是把删除逻辑单独放在 WHEN NOT MATCHED BY SOURCE THEN DELETE 分支里。例如:
MERGE INTO orders o USING (SELECT order_id, status FROM new_orders) n ON (o.order_id = n.order_id) WHEN MATCHED THEN UPDATE SET o.status = n.status WHEN NOT MATCHED THEN INSERT (order_id, status) VALUES (n.order_id, n.status) WHEN NOT MATCHED BY SOURCE THEN DELETE;
注意:DELETE 后不能加 WHERE ——它天然作用于所有“源中未出现的目标行”。如果需要过滤(比如只删状态为 'ARCHIVED' 的),得提前用视图或子查询限制 USING 的源集,而不是在 DELETE 后加条件。
ON 条件必须覆盖全量主键,否则 DELETE 会误删
ON 子句定义的匹配逻辑,直接决定哪些行算“源中存在”。如果只用部分字段(比如只写 ON (o.id = n.id) 却忽略业务上真正的联合唯一键),会导致 Oracle 把多个目标行映射到同一个源行,进而触发“无法多次更新/删除同一行”的报错,或者更隐蔽地——让本该保留的旧数据被 WHEN NOT MATCHED BY SOURCE 误判为“源中不存在”而删掉。
典型踩坑场景:
- 目标表主键是
(order_id, line_no),但ON只写了o.order_id = n.order_id→ 删除时可能连带删掉其他line_no行 - 源子查询未去重,导致一个
order_id出现多次 →MERGE报ORA-30926: unable to get a stable set of rows in the source tables - 源表含 NULL 值,而
ON条件用=比较 → NULL 不等于任何值,包括 NULL 自身,这些行永远进不了WHEN MATCHED,却也不会进WHEN NOT MATCHED(因为ON不成立),最终被WHEN NOT MATCHED BY SOURCE误删
DELETE 不支持 RETURNING,且事务不可拆分
Oracle MERGE 中的 DELETE 分支不支持 RETURNING 子句,没法像单独 DELETE 那样捕获被删行的 ID 或数量。如果你依赖删除反馈做后续处理(比如记录日志、触发下游清理),得改用两步法:先查出待删 ID 列表,再执行 DELETE + RETURNING。
另外,整个 MERGE 是原子操作,UPDATE、INSERT、DELETE 共享同一事务上下文。这意味着:哪怕只有一行触发了 DELETE 失败(比如违反外键约束),整个语句都会回滚——不像分开写的 DML 那样可以局部失败。生产环境批量同步时,务必确保目标表的外键、触发器、约束不会意外拦截 DELETE。


















