ROW_NUMBER()仅编号,不直接去重或删除;需配合PARTITION BY定义分组、ORDER BY确定保留顺序,并用WHERE rn=1或DELETE筛选,且必须用ROW_NUMBER()而非RANK()等避免并列。

ROW_NUMBER() 本身不删除数据,也不能直接去重;它只编号。真正去重靠的是用它标记“该留哪一行”,再配合 WHERE 或 DELETE 筛掉其他行。
必须写全 PARTITION BY 和 ORDER BY
漏掉 PARTITION BY,ROW_NUMBER() 就会对整张表从 1 开始连续编号,结果只剩第一行——这不是按业务逻辑去重,是随机截断。漏掉 ORDER BY,数据库可能按任意顺序编号,同一语句多次执行结果不同。
-
PARTITION BY user_id, product_id:定义“哪些行算重复”,即分组依据 -
ORDER BY created_at DESC, id DESC:确保最新、且时间相同时主键大的排前面,避免不确定性 - 没索引的
PARTITION BY字段 + 大数据量 → 排序慢,建议建复合索引:(user_id, created_at)
WHERE rn = 1 是去重动作的关键
子查询或 CTE 中生成 rn 后,外层 WHERE rn = 1 才真正实现“每组留一条”。注意不能直接在 SELECT 里写 ROW_NUMBER() = 1,语法不支持。
- 查保留结果:
SELECT user_id, product_id, created_at
FROM (
SELECT user_id, product_id, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id, product_id ORDER BY created_at DESC, id DESC) AS rn
FROM orders
) t
WHERE rn = 1;
- 删多余行(MySQL 8.0+/PostgreSQL/SQL Server):
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY updated_at DESC, id DESC) AS rn
FROM orders
)
DELETE FROM orders WHERE id IN (SELECT id FROM ranked WHERE rn > 1);
- 线上执行前务必先跑:
SELECT COUNT(*) FROM ranked WHERE rn > 1,确认要删多少行
别用 RANK() 或 DENSE_RANK() 做去重
RANK() 和 DENSE_RANK() 遇到 ORDER BY 值相同时会并列赋值,比如都给 1,导致 WHERE rn = 1 留下多行——去重失败。
-
ROW_NUMBER()强制唯一编号:即使created_at完全一样,也会按隐式顺序(或你加的id DESC)分出 1、2、3… - 只要目标是“每组严格一条”,必须用
ROW_NUMBER(),且ORDER BY末尾补一个唯一字段(如主键id) -
NULL值在ORDER BY中排序不稳定,可用COALESCE(updated_at, '1970-01-01')或数据库特有写法(如 PostgreSQL 的NULLS LAST)显式控制
兼容性和替代方案的边界在哪
ROW_NUMBER() 在 MySQL 8.0+、PostgreSQL、SQL Server、Oracle、SQLite 3.25+ 支持;老版本 SQLite 或 MySQL 5.7 不支持窗口函数,会报错 no such function: ROW_NUMBER。
- 如果只要某几列的唯一组合(如只取
DISTINCT user_id),SELECT DISTINCT user_id更快、更简单 - 如果要“每个用户最新订单的完整行”,
GROUP BY无法安全返回非聚合字段(MySQL 5.7+ 默认报错,即使绕过也返回随机值) -
QUALIFY可简化写法(如QUALIFY ROW_NUMBER() OVER (...) = 1),但仅 BigQuery/Snowflake/Trino 支持,主流关系库仍需子查询或 CTE
真正难的不是写对 ROW_NUMBER(),而是说清“哪一行才算重复里的‘代表’”——是按时间?按状态?还是按主键大小?这个业务定义一旦模糊,后面所有排序和筛选都会漂移。

















