sql server 的 merge 语句必须显式写出全部三个 when 分支(matched、not matched by target、not matched by source),缺一即报语法错误;on 字段需索引,target.id 应为主键,insert/update 需满足约束,须用 try/catch 处理事务,推荐用 output 获取操作类型。

MERGE INTO 在 SQL Server 存储过程中必须写全三个分支,否则直接报错 Incorrect syntax near the keyword 'MERGE' —— 这不是风格问题,是语法硬性要求。
SQL Server 的 MERGE 必须显式写出全部三个 WHEN 分支
很多人照着 Oracle 或 PostgreSQL 的写法,在 SQL Server 里只写 WHEN MATCHED 和 WHEN NOT MATCHED BY TARGET,结果执行就失败。SQL Server 强制要求你明确声明 WHEN NOT MATCHED BY SOURCE 分支(哪怕只写 THEN DELETE 或空操作 THEN UPDATE SET @dummy = @dummy)。
- 漏掉
WHEN NOT MATCHED BY SOURCE→ 报错Incorrect syntax near the keyword 'MERGE' - 只写
WHEN MATCHED→ 同样报错,不接受“默认忽略”逻辑 - 实际业务中若不需要处理“目标有、源无”的情况,可写成
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET @dummy = 0(用变量占位),或更清晰地写WHEN NOT MATCHED BY SOURCE THEN DELETE并加注释说明“此处为语法补全,当前业务不触发”
ON 条件字段必须有索引,否则性能断崖式下跌
MERGE 本质先做一次左连接判断匹配状态,再分发执行分支。如果 ON 中的字段(比如 t.id = s.id)没索引,SQL Server 会扫描目标表和源表各一遍 —— 百万级数据下可能从毫秒变分钟。
-
target.id必须是主键或唯一索引(推荐主键) -
source.id建议也加非聚集索引,尤其当 source 是 CTE 或临时表时 - 避免在
ON里用函数,例如ON UPPER(t.email) = UPPER(s.email)→ 索引失效 - 过滤逻辑(如
WHERE is_valid = 1)应提前放到USING的子查询里,别塞进ON
INSERT 和 UPDATE 字段必须满足 NOT NULL 和类型约束
看似只是写个语句,但字段对不上就会在运行时报错,比如目标表 status 列设了 NOT NULL,而 INSERT 分支漏写了它,或传了 NULL 值。
-
INSERT (col1, col2)的列名顺序不必和表定义一致,但值列表必须一一对应且类型兼容 -
UPDATE SET t.col1 = s.col1中,s.col1不能为NULL且目标列不允许为空 → 直接报错 - 建议在存储过程开头用
SET NOCOUNT ON,避免 MERGE 返回多结果集干扰调用方 - 如果源数据可能含重复键(如两次导入同一订单号),MERGE 会在
WHEN NOT MATCHED BY TARGET阶段触发主键冲突,需提前去重或用GROUP BY+ROW_NUMBER()裁剪
在存储过程中调用 MERGE 要注意事务与错误处理
MERGE 是原子操作,但整个存储过程未必是。如果 MERGE 失败(比如约束冲突),后续语句仍可能继续执行,导致数据不一致。
- 务必用
BEGIN TRY / BEGIN CATCH包裹 MERGE 语句 - 不要依赖 MERGE 自身的返回值判断成功;
@@ROWCOUNT只反映最后执行的分支影响行数,无法区分是 INSERT 还是 UPDATE - 若需记录每类操作数量(如“更新了5行,插入了2行”),得用
OUTPUT子句捕获动作类型:OUTPUT $action, inserted.id - 慎用
TOP (n)修饰 MERGE —— 它只限制匹配行数,不保证源数据顺序,容易漏数据
最常被忽略的一点:MERGE 的 ON 条件和后续 WHEN 里的 AND 条件不是一回事。前者决定“是否进入该分支”,后者决定“进入后是否执行”。很多逻辑错误其实出在把业务过滤条件错放在 ON 里,导致本该更新的行被跳过。










