sql server merge 必须显式写 when not matched by target 和 by source,on 条件需确定性等值联接,delete 不触发触发器且不支持 set,output 是唯一可审计手段。

WHEN NOT MATCHED BY TARGET 和 WHEN NOT MATCHED BY SOURCE 必须显式写出
只写 WHEN NOT MATCHED THEN INSERT 是不够的——SQL Server 默认它等价于 WHEN NOT MATCHED BY TARGET,但删除操作必须靠 WHEN NOT MATCHED BY SOURCE 触发。漏掉 BY SOURCE 会导致语法错误或逻辑失效。
常见错误现象:Msg 156, Level 15, State 1, Line X: Incorrect syntax near the keyword 'WHEN',或明明源表少了几行,目标表却没删。
-
WHEN NOT MATCHED BY TARGET:源有、目标无 → 插入 -
WHEN NOT MATCHED BY SOURCE:目标有、源无 → 删除(不能写成 UPDATE) - 两个分支不能合并,也不能省略
BY TARGET或BY SOURCE
ON 条件必须是确定性等值联接,否则 MERGE 直接报错
MERGE 的 ON 子句不是普通 JOIN,它决定了每行归属哪一类分支。一旦条件含函数、LIKE、隐式转换或空值参与比较,就可能触发 The MERGE statement attempted to UPDATE or DELETE the same row more than once。
典型陷阱:
-
ON t.email = UPPER(s.email)→ 函数导致索引失效,且可能多对一匹配 -
ON t.id = s.id OR t.code = s.code→ 非等值逻辑,SQL Server 不允许 -
ON ISNULL(t.email, '') = ISNULL(s.email, '')→ 空值归并引发重复匹配
正确做法:确保字段类型一致、非空、有唯一索引;必要时提前在 USING 子查询中清洗出确定性键。
DELETE 操作不能依赖触发器,时间戳需显式赋值
WHEN NOT MATCHED BY SOURCE THEN DELETE 是直接删行,不走 AFTER DELETE 触发器。如果业务要求记录删除时间或软删标记,必须在 UPDATE 分支里处理,或者改用软删逻辑(即 UPDATE 标记 + WHERE 过滤)。
另外,DELETE 分支本身不支持 SET,所以无法在删除前更新字段。若需审计,只能:
- 先用
OUTPUT DELETED.*记录被删数据 - 或把删除逻辑拆成两步:先
UPDATE标记为已删除,再另起语句清理 - 避免在
DELETE分支中引用INSERTED——它为空
大批量同步时,不加 OUTPUT 就等于“黑盒执行”
你无法从 MERGE 返回值知道哪几行被删、哪几行被插。不加 OUTPUT $ACTION, DELETED.id, INSERTED.id,就等于执行了但没留痕。线上任务一旦出问题,排查成本极高。
关键约束:
-
OUTPUT必须紧跟在所有WHEN分支之后、分号之前 - 只能引用
INSERTED或DELETED中出现的列,不能写t.updated_at - 若要捕获删除前的状态,必须写
OUTPUT $ACTION, DELETED.*,不能只写DELETED.id
真正危险的不是语法写错,而是逻辑看似跑通、数据却静默漂移——尤其是当源表本身含重复键、或 ON 条件未覆盖全部业务唯一维度时。










