merge在sql server中必须同时指定when matched和when not matched by target分支,on条件需等值匹配且字段类型一致,目标表联接列须有索引,源数据需去重,并通过output $action确认操作类型。

MERGE 在 SQL Server 里不是“INSERT 或 UPDATE 二选一”,而是“匹配就 UPDATE,不匹配就 INSERT”——它天然适合做 upsert(插入或更新)。
WHEN MATCHED 和 WHEN NOT MATCHED BY TARGET 必须同时写
只写 WHEN MATCHED 会报错:The MERGE statement attempted to UPDATE or DELETE the same row more than once;只写 WHEN NOT MATCHED(没带 BY TARGET)虽然语法通过,但逻辑上默认就是 BY TARGET,可一旦源数据有重复键,仍会触发上述错误。
- 必须显式写出两个分支:
WHEN MATCHED THEN UPDATE SET ...和WHEN NOT MATCHED BY TARGET THEN INSERT ... -
ON条件必须用括号包裹,比如ON (t.id = s.id),不能写成ON t.id = s.id - 目标表在
INTO后不能加别名,INTO target_table AS t是非法的;但USING后的源可以加别名,比如USING (...) AS s -
UPDATE SET左侧只能是目标表列(如t.name),右侧可用源别名字段(如s.name),不能写source.name(除非你真把子查询 alias 成了source)
ON 条件必须是确定性等值联接,否则直接失败
常见翻车点:用 LIKE、函数转换、隐式类型转换(比如 int vs varchar)、或 IS NULL 做 ON 条件。SQL Server 会拒绝执行,并报错:The MERGE statement attempted to UPDATE or DELETE the same row more than once。
- 确保联接字段类型完全一致(比如都为
INT或都为NVARCHAR(50)),避免COLLATE强制转换 - 目标表的联接字段必须有索引(最好是主键或唯一索引),否则性能暴跌,且容易锁表
- 如果源数据含重复键(比如两条
s.id = 1),MERGE 直接报错,得先去重:USING (SELECT DISTINCT id, name FROM staging) AS s
OUTPUT $ACTION 是唯一可靠方式确认操作类型
你无法靠 @@ROWCOUNT 区分本次到底插了几行、更了几行。漏掉 OUTPUT 就等于“干完了但不知道干了啥”,线上同步任务出问题时根本没法定位。
- 加一句:
OUTPUT $ACTION, INSERTED.id, DELETED.id,返回结果中$ACTION字段值为'INSERT'或'UPDATE' -
OUTPUT必须紧跟在所有WHEN子句之后、语句结尾分号之前 - 不能引用未出现在
INSERTED或DELETED中的列,比如OUTPUT s.name是非法的
真正麻烦的不是语法写不对,而是 ON 条件设计不合理 + 源数据没清洗干净 —— 这两类问题不会立刻报错,但会在并发或大数据量时突然卡死、锁表、或产生脏数据。动手前先查源表 COUNT(*) 和 COUNT(DISTINCT join_key) 是否相等,再看目标表对应字段有没有索引,比反复试跑更省时间。










