唯一安全删法是用cte封装row_number()再delete,因sql server禁止delete中直接嵌套窗口函数;cte需显式别名,partition by和order by须严格匹配去重逻辑,且执行前必须select验证并备份。

直接用 ROW_NUMBER() 配合 CTE 是唯一安全、可预测的删法,其他写法要么报错,要么误删。
为什么不能在 DELETE 里直接嵌套 ROW_NUMBER() 子查询
SQL Server 禁止在 DELETE 的 FROM 子句中引用同一张表的窗口函数结果。常见错误写法:
DELETE FROM orders
WHERE id IN (
SELECT id FROM (
SELECT id, ROW_NUMBER() OVER (PARTITION BY user_id, product_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn > 1
);
会直接报错:Cannot specify target table 'orders' for update in FROM clause 或 Invalid column name 'rn'。根本原因是子查询无法同时读写同表,且窗口别名 rn 在外层不可见。
CTE 是绕过限制的唯一可靠方式
必须把 ROW_NUMBER() 结果封装进带别名的 CTE,再对 CTE 执行 DELETE:
-
CTE必须有显式别名(如AS d),否则DELETE FROM d会失败 -
PARTITION BY列要严格对应你定义的“重复依据”,比如去重依据是email和phone,就写PARTITION BY email, phone -
ORDER BY决定哪条被保留:按时间倒序(created_at DESC)留最新;按主键正序(id ASC)留最早 - 如果排序字段允许
NULL,ORDER BY created_at DESC会让NULL排第一,导致误删——建议加IS NULL处理,或改用非空列如id
正确示例(保留每个 user_id + product_id 组中 created_at 最大的记录):
WITH d AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id, product_id
ORDER BY created_at DESC, id DESC
) AS rn
FROM orders
)
DELETE FROM d WHERE rn > 1;
执行前必须验证,否则极易全表清空
真实事故多源于漏写 WHERE rn > 1 或复制模板时删错了行:
- 先运行
SELECT *查看将删哪些行:WITH d AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id, product_id ORDER BY id DESC) AS rn FROM orders ) SELECT * FROM d WHERE rn > 1;
- 确认无误后再执行
DELETE,千万别跳过这步 - 生产环境务必先备份表,或在事务里执行:
BEGIN TRAN; ...; ROLLBACK;测试完再COMMIT - 若表无时间字段也无自增主键,
ORDER BY (SELECT 0)虽语法通过,但保留哪条完全随机,业务上不可控
最易被忽略的是 ORDER BY 字段的 NULL 处理和 CTE 别名缺失——这两处出错不会报语法错误,但会导致逻辑错误或执行失败。动手前花 30 秒检查这两点,能避开 90% 的线上事故。











