窗口函数不能直接删除数据,必须先用cte或子查询标记重复行(如row_number() over(partition by col order by id)),再基于编号删除rn>1的行;执行前须用select验证并备份。

窗口函数不能直接删除数据
窗口函数(如 ROW_NUMBER()、RANK())本身是查询计算工具,不支持 DELETE 或 UPDATE 操作。试图写成 DELETE FROM t WHERE ROW_NUMBER() OVER (...) > 1 会报错——SQL 不允许在 WHERE 子句里直接用窗口函数。
用窗口函数识别重复行再删,分两步走
核心思路:先用窗口函数标记重复行(比如给每组重复数据按序编号),再基于这个编号做删除。实际执行必须拆成子查询或 CTE。
- PostgreSQL / SQL Server / Oracle / MySQL 8.0+ 支持 CTE + 窗口函数,推荐写法:
WITH dup AS (
SELECT id, ROW_NUMBER() OVER (
PARTITION BY col1, col2 ORDER BY updated_at DESC
) AS rn
FROM orders
)
DELETE FROM orders
WHERE id IN (SELECT id FROM dup WHERE rn > 1);
- MySQL 5.7 或不支持 CTE 的环境,得套一层子查询(注意:MySQL 不允许在子查询中直接引用目标表,需加多层包装):
DELETE FROM orders
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id
FROM orders
GROUP BY col1, col2
) AS keep
);
-
PARTITION BY列要和业务去重逻辑一致(比如按订单号+用户ID去重,就写PARTITION BY order_id, user_id) -
ORDER BY决定哪条被保留:想留最新的一条,就按时间倒序;想留 ID 最小的,就按id ASC
DELETE 时没加 WHERE 条件导致全表清空
这是真实发生过的事故。CTE 或子查询如果漏写 rn > 1 或 NOT IN 条件,DELETE 就变成无条件删全表。
一款AI数据处理工具,主要用于用于查询 Massive 市场数据端点的 Bash CLI 封装和 OpenClaw 技能,适用于 Codex 或 OpenClaw 代理从 shell 调用,适合需要提升相关任务效率的用户。
- 务必在执行前先用
SELECT验证要删哪些行:
SELECT * FROM dup WHERE rn > 1;
- 生产环境操作前,先备份关键字段或建临时表存待删 ID
- 如果表很大,
DELETE可能锁表或慢,考虑分批删(例如每次删 1000 行,用LIMIT或TOP控制)
不同数据库对窗口函数 + DELETE 的语法容忍度不同
MySQL 8.0+ 允许 CTE 后接 DELETE,但 PostgreSQL 要求 CTE 必须是 WITH ... AS (SELECT ...) 形式,且 DELETE 不能直接引用 CTE 名(得用子查询套一层)。SQL Server 对 CTE 中的窗口函数支持最宽松,但 DELETE 仍不能直接写 FROM cte,必须 FROM table WHERE id IN (SELECT id FROM cte)。
- 别依赖“写了就能跑”,先查你用的数据库版本是否支持该组合
- 用
EXPLAIN或执行计划确认删除是否走索引(PARTITION BY字段最好有联合索引)
真正麻烦的不是写法,而是确定“哪些算重复”——业务规则变了,PARTITION BY 和 ORDER BY 就得跟着调,一不留神就删错行。










