
当 merge 语句返回“0 行受影响”却预期应插入或更新数据时,往往并非语法错误,而是因源表(using 子查询)无匹配行导致整个操作静默跳过——这是 merge 的设计逻辑,而非 bug。
当 merge 语句返回“0 行受影响”却预期应插入或更新数据时,往往并非语法错误,而是因源表(using 子查询)无匹配行导致整个操作静默跳过——这是 merge 的设计逻辑,而非 bug。
在您提供的示例中,MERGE ore AS T_Base USING (SELECT ... FROM ore WHERE date = '2022-9-20' AND sec = '2' AND shift = '1') AS T_Source ... 的核心问题在于:源子查询(T_Source)本身从目标表 ore 中筛选数据。若表中根本不存在 date = '2022-09-20' AND sec = '2' AND shift = '1' 的记录,则 T_Source 结果集为空 → MERGE 没有任何行可匹配或插入 → 自然返回“0 行受影响”。
这并非 SQL Server 的缺陷,而是 MERGE 的严格语义:它只对 USING 提供的每一行执行匹配逻辑;若源集为空,整个语句不触发任何 WHEN MATCHED 或 WHEN NOT MATCHED 分支。
✅ 正确做法是:确保 USING 子查询提供独立、可控的源数据,而非依赖目标表当前状态。例如,使用常量值、临时表、参数化 CTE 或应用层传入的确定性数据:
-- ✅ 推荐:使用 VALUES 构造明确的源数据(无需依赖目标表内容)
MERGE ore AS T_Base
USING (VALUES
('2022-09-20', 2, 1) -- date, sec, shift
) AS T_Source(date, sec, shift)
ON T_Base.date = T_Source.date
AND T_Base.sec = T_Source.sec
AND T_Base.shift = T_Source.shift
WHEN MATCHED THEN
UPDATE SET id_ore = 1 -- 仅更新业务字段,避免冗余赋值(date/sec/shift 已在 ON 中保证一致)
WHEN NOT MATCHED THEN
INSERT (date, sec, shift, id_ore)
VALUES (T_Source.date, T_Source.sec, T_Source.shift, 1);⚠️ 注意事项:
-
避免在
USING中反向查询目标表:如原语句FROM ore WHERE ...,易造成逻辑死锁和空源集; -
ON条件需覆盖完整唯一键逻辑:当前表结构缺少显式主键/唯一约束,建议为(date, sec, shift)添加唯一索引,防止重复插入; -
谨慎处理
UPDATE中的冗余赋值:原语句对date,sec,shift重复赋值无意义且降低可读性,应只更新需变更字段(如id_ore); -
防御 SQL 注入:答案中给出的拼接式
IF EXISTS...UPDATE/INSERT方案虽能工作,但直接拼接变量(如$y,$sec)存在严重注入风险,必须改用参数化查询(如 SQL Server 的sp_executesql+ 参数占位符); -
MERGE的原子性优势仍在:相比IF EXISTS分两步,MERGE在并发场景下可避免竞态条件(如两次检查间被其他会话插入),因此推荐修复源数据构造方式,而非弃用MERGE。
总结:MERGE 返回 0 行的根本原因,90% 源于 USING 子句未返回预期数据行。排查时请优先验证子查询结果集;重构时坚持“源独立、条件精准、更新精简”三原则,即可安全发挥 MERGE 的高效 Upsert 能力。

















