MERGE 是 SQL Server 2019 中唯一原生 Upsert 语句,但必须显式定义 ON 匹配条件、WHEN MATCHED 更新分支和 WHEN NOT MATCHED 插入分支,三者缺一不可;漏写任一分支将报错,且 ON 必须基于非 NULL 唯一键、避免函数导致索引失效,源数据需预去重并加 HOLDLOCK 保障并发安全。

MERGE 是 SQL Server 2019 中唯一原生支持 Upsert(插入或更新)的语句,但它不是“自动识别重复就更新”的黑盒——必须显式定义匹配逻辑、更新条件和插入分支,否则容易误删、误插或静默失败。
如何写一个安全的 MERGE 基础结构
核心是三个子句缺一不可:MATCH(用 ON 定义连接条件)、WHEN MATCHED(更新)、WHEN NOT MATCHED(插入)。漏掉 WHEN NOT MATCHED BY SOURCE 不影响 Upsert,但若后续要同步删除,就得补上。
常见错误现象:MERGE 报错 “The target table 'table_name' of the DML statement cannot have any enabled triggers”,是因为目标表有启用的 AFTER 触发器——SQL Server 不允许在有这类触发器的表上执行 MERGE。
- 始终把
ON条件写成等值判断(如t.id = s.id),避免使用函数或表达式,否则无法走索引且可能匹配多行 - 更新分支建议加上
AND限制(如WHEN MATCHED AND t.status != s.status),避免无意义的更新引发日志膨胀 - 插入分支的
VALUES必须与目标表列顺序或显式列名严格对应,不推荐依赖隐式顺序
MERGE 中的 OUTPUT 子句怎么拿变更结果
OUTPUT 是获取 Upsert 实际行为的唯一可靠方式,比如区分哪几行被插入、哪几行被更新。它不能放在语句末尾单独写,必须紧跟在 MERGE 主体后、分号前。
使用场景:ETL 中需要记录处理明细,或下游系统依赖变更类型做不同响应。
示例片段:
OUTPUT $action, inserted.id, inserted.name, deleted.updated_at
注意:deleted 只在 WHEN MATCHED 分支中有效;inserted 在 INSERT 和 UPDATE 中都存在;$action 返回字符串 'INSERT' 或 'UPDATE'。
- 别在
OUTPUT里引用未在MATCHED/NOT MATCHED中实际涉及的列,会报错 - 如果想把
OUTPUT结果存入临时表,必须用INTO #tmp,不能用SELECT * FROM (MERGE ...) AS t——MERGE不支持直接嵌套在子查询中
为什么 MERGE 有时会报“Violation of PRIMARY KEY constraint”
这不是 MERGE 本身的问题,而是源数据存在重复键,导致多个源行匹配同一个目标行,进而触发多次 UPDATE 或(更危险的)多个源行都想 INSERT 同一键值。
典型诱因:ON 条件太宽(如只用 name 匹配,但 name 不唯一),或源数据没去重(如从视图或 JOIN 结果取数时产生笛卡尔积)。
- 上线前务必对源数据执行
GROUP BY+HAVING COUNT(*) > 1检查主键/唯一键重复 - 在
MERGE外层套一层 CTE 或临时表,预先DISTINCT或ROW_NUMBER() OVER (PARTITION BY key ORDER BY ...)去重 - 不要依赖
IGNORE_DUP_KEY = ON来兜底——它对MERGE的INSERT分支无效
真正难的不是写出语法正确的 MERGE,而是确认 ON 条件是否覆盖所有业务意义上的“同一行”,以及源数据是否真的满足集合语义——这两点出问题,语句越“成功”越危险。










