用 ROW_NUMBER() + CTE 是最稳妥、可读性高且能精确控制“最新”定义的方式;DISTINCT 和 GROUP BY 无法保留完整行数据,不能用于删重。

直接说结论:用 ROW_NUMBER() + CTE 是最稳妥、可读性高、且能精确控制“最新”定义的方式;别用 DISTINCT 或单纯 GROUP BY,它们没法保留完整行数据。
为什么不能用 DISTINCT 或 GROUP BY 直接删重复?
因为它们只解决“查出来不重复”,不解决“删哪些、留哪条”。DISTINCT 会丢字段(比如你有 ID、ProductName、OrderDate、Amount,DISTINCT ProductName, OrderDate 就没法知道该留哪条 Amount);GROUP BY 必须配合聚合函数(如 MAX(OrderDate)),但你无法同时拿到对应那条记录的 ID 或 Amount,除非用子查询或窗口函数。
ROW_NUMBER() 在存储过程中怎么写才安全?
关键点是:必须把排序逻辑和分区逻辑写清楚,且避免在生产环境直接 DELETE 前不验证。
- 先用
SELECT查出要删的记录(确认逻辑):WITH dup AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY ProductName ORDER BY OrderDate DESC, ID DESC ) AS rn FROM Sales ) SELECT * FROM dup WHERE rn > 1; - 再在存储过程中封装删除逻辑,加事务和影响行数检查:
BEGIN TRY BEGIN TRANSACTION; <p>WITH dup AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY ProductName ORDER BY OrderDate DESC, ID DESC ) AS rn FROM Sales ) DELETE FROM dup WHERE rn > 1;</p><p>IF @@ROWCOUNT = 0 PRINT '无重复数据'; ELSE COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; THROW; END CATCH
-
PARTITION BY字段必须是业务上定义“重复”的依据(比如ProductName),ORDER BY要明确“最新”的标准——时间字段优先,有并列时用ID或SequenceID保序
存储过程里执行前容易踩的坑
不是语法错,而是逻辑和运维风险:
- 没加
WHERE条件就全表跑ROW_NUMBER():大表会卡住,建议先加WHERE OrderDate >= DATEADD(MONTH, -6, GETDATE())缩小范围 - 把
OrderDate当成唯一判据,但实际存在同秒插入的多条记录:这时仅靠OrderDate DESC无法保证稳定选中一条,必须补一个确定性字段(如ID DESC) - 存储过程没设
SET XACT_ABORT ON:出错时事务可能没回滚,导致部分删、部分留 - 直接在原始表上操作,没备份或没走清洗层:应优先在
Sales_Clean表上操作,原始表只读
真正难的不是写出那几行 SQL,而是搞清“哪个字段组合才算重复”“最新到底按什么定”“删完会不会影响下游报表的主键引用”。这些必须和业务方对齐,而不是光看数据库里有没有重复值。

















