不能直接在存储过程中套用DELETE+ROW_NUMBER(),因其语法不被支持且缺乏事务安全与并发控制;必须通过CTE或子查询封装,并严格遵循识别→策略→执行→验证四步流程,结合MERGE或分页处理保障数据一致性与性能。

为什么不能直接在存储过程中套用 DELETE + ROW_NUMBER()?
因为去重不是“删掉重复行”就完事,而是要明确留哪条、依据什么判重、是否影响下游外键或触发器。存储过程里如果只写 DELETE FROM t WHERE id NOT IN (SELECT MIN(id) FROM t GROUP BY key_cols),在并发写入时大概率出现主键冲突或漏删——GROUP BY没加事务隔离,中间状态可能被其他会话修改;而且这种写法在千万级表上会全表扫描+临时排序,锁住整张表几十秒。
去重合并必须拆成“识别→保留策略→执行→验证”四步
真实业务中,MERGE 是唯一能在一个语句里安全完成“查重+留新+删旧+插补”的操作,但前提是源数据干净、ON 条件覆盖全部业务唯一键、且目标表有对应索引。在存储过程中,必须显式控制这四个环节:
- 先用
SELECT ... INTO #dedup_candidates把待处理数据捞进临时表,并在上面建唯一索引(如CREATE UNIQUE INDEX IX_tmp ON #dedup_candidates(email, tenant_id)),强制去重并提前暴露冲突 -
ON条件必须包含所有业务唯一字段,比如用户同步场景不能只写ON t.id = s.id,而要写ON t.email = s.email AND t.tenant_id = s.tenant_id -
WHEN MATCHED THEN UPDATE分支里,对可能为NULL的字段用ISNULL(s.col, t.col),避免把原值覆盖成空 - 每批最多处理 5000 行,用
OFFSET / FETCH分页,配合BEGIN TRY / BEGIN CATCH捕获The MERGE statement attempted to update or delete the same row more than once这类错误
怎么让存储过程支持不同表的动态去重合并?
硬编码字段名会失去复用性。正确做法是把去重维度(@DedupKeys)、保留策略(@OrderBy,如 updated_at DESC)、目标表名(@TargetTable)作为参数传入,再用 sys.dm_exec_describe_first_result_set 或查询 sys.columns 动态拼出 INSERT (col1,col2,...) 和 VALUES (s.col1,s.col2,...) 列表。但注意:动态 SQL 中不能直接拼 ORDER BY 子句进 ROW_NUMBER(),得用 REPLACE 替换占位符,否则会报错 Incorrect syntax near 'ORDER'。
最容易被忽略的三个点
一是 MERGE 绕过 AFTER 触发器,如果业务逻辑依赖触发器更新审计字段,必须在 UPDATE 分支里显式赋值 updated_at = GETDATE();二是没有主键的表,WHEN NOT MATCHED BY SOURCE THEN DELETE 会误删全表,务必加 WHERE t.status != 'archived' 类条件兜底;三是临时表字段类型要和目标表严格一致,比如目标表 email NVARCHAR(254),临时表建 NVARCHAR(100) 就会导致隐式转换,索引失效。

















