MERGE的DELETE子句指WHEN NOT MATCHED BY SOURCE THEN DELETE,仅SQL Server 2016+和PostgreSQL 15+支持,需目标表有唯一约束且ON条件为等值匹配,MySQL及旧版SQL Server不支持。

什么是MERGE的DELETE子句及其触发条件
MERGE语句本身不直接支持“删除目标表中源端不存在的记录”,必须显式启用WHEN NOT MATCHED BY SOURCE THEN DELETE子句,且该子句仅在SQL Server 2016+ 和 PostgreSQL 15+(via MERGE ... USING ... WHEN NOT MATCHED BY SOURCE THEN DELETE)中受支持。MySQL和早期SQL Server版本(如2014及之前)**完全不支持此语法**,强行使用会报错Incorrect syntax near 'DELETE'或Unknown keyword "DELETE"。
关键前提是:目标表必须有唯一约束(如主键或唯一索引),否则NOT MATCHED BY SOURCE无法可靠判断哪些行“在源中不存在”——因为匹配逻辑依赖于ON子句中的关联字段是否能唯一定位目标行。
SQL Server中正确写法与常见错误
在SQL Server中,要同步删除目标表中源端已消失的数据,必须同时满足三个条件:启用NOT MATCHED BY SOURCE、ON条件覆盖完整主键、目标表有唯一约束。典型写法如下:
MERGE dbo.users AS tgt USING (SELECT id, name, email FROM staging_users) AS src ON tgt.id = src.id WHEN MATCHED THEN UPDATE SET name = src.name, email = src.email WHEN NOT MATCHED BY SOURCE THEN DELETE;
- ON子句中必须用
tgt.id = src.id这类等值匹配,不能含函数或表达式(如ISNULL(tgt.id, 0) = src.id),否则NOT MATCHED BY SOURCE被忽略 - 若
staging_users里有重复id,MERGE会报错The MERGE statement attempted to UPDATE or DELETE the same row more than once,需提前去重 - DELETE操作不可回滚到某一行——整条MERGE要么全提交,要么全回滚;若需审计删除记录,得在DELETE前用
OUTPUT DELETED.* INTO #deleted_log
PostgreSQL 15+ 的等效实现与限制
PostgreSQL从15开始支持WHEN NOT MATCHED BY SOURCE THEN DELETE,但语法更严格:必须显式声明MATCHED和NOT MATCHED BY SOURCE分支,且不允许只写DELETE而不写INSERT/UPDATE。示例如下:
MERGE INTO users AS tgt USING staging_users AS src ON tgt.id = src.id WHEN MATCHED THEN UPDATE SET name = src.name, email = src.email WHEN NOT MATCHED BY SOURCE THEN DELETE;
- 不支持
WHEN NOT MATCHED THEN INSERT与WHEN NOT MATCHED BY SOURCE THEN DELETE共存于同一MERGE(会报错conflicting WHEN clauses) - DELETE不触发
AFTER DELETE触发器,但会触发BEFORE DELETE(如果定义了) - 若目标表无主键,PostgreSQL允许执行,但
NOT MATCHED BY SOURCE行为不可预测——它按物理顺序扫描,可能漏删或多删
没有NOT MATCHED BY SOURCE时的替代方案
在MySQL、SQLite或旧版SQL Server中,只能用两步模拟:先标记待删ID,再DELETE。核心是避免WHERE NOT IN (subquery)因NULL导致逻辑错误:
-- 安全写法:用NOT EXISTS代替NOT IN DELETE FROM users WHERE NOT EXISTS ( SELECT 1 FROM staging_users s WHERE s.id = users.id );
- 若
staging_users.id允许NULL,NOT IN (SELECT id FROM staging_users)结果恒为UNKNOWN,整条DELETE不生效 - 大表慎用:DELETE前务必在
staging_users.id和users.id上建索引,否则全表扫描+嵌套循环极慢 - 想原子化?把SELECT ID存临时表 + JOIN DELETE,比子查询更容易加索引和分批控制
真正难的不是写DELETE,而是确认“源端不存在”这个判断是否符合业务语义——比如软删除的记录该不该被同步删掉,时间窗口内延迟到达的数据会不会误删。这些得靠上游数据质量保障,不是MERGE语法能解决的。

















