row_number()不能直接去重,仅能编号;真正去重需通过partition by分组、order by排序(如created_at desc)、外层where rn=1筛选,以保留每组首行。

ROW_NUMBER() 不能直接去重,但能帮你选出每组中“想要的那一条”——关键在配合 PARTITION BY 和 ORDER BY 控制排序逻辑。
为什么 ROW_NUMBER() 不是去重函数?
它只是给结果集里的每一行按规则打上序号,不会删数据。真正去重要靠外部筛选,比如只取 ROW_NUMBER() = 1 的行。
常见错误现象:
– 写了 SELECT *, ROW_NUMBER() OVER (...) FROM t 就以为重复没了
– 忘记 PARTITION BY,导致全表编号,分组失效
– ORDER BY 选错字段,比如用不稳定字段(如 id 随机)导致每次运行结果不一致
- 去重目标必须明确:保留最新时间?最小ID?最高优先级值?
-
PARTITION BY字段必须是你要“按什么分组”的依据(比如user_id、order_no) -
ORDER BY要选确定性字段;若需稳定结果,可加二级排序,例如ORDER BY created_at DESC, id DESC
典型场景:按 user_id 保留最新一条记录
假设表 user_login_log 有重复用户登录记录,你想每个 user_id 只留 created_at 最大的那条:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY created_at DESC, id DESC
) AS rn
FROM user_login_log
) t WHERE rn = 1;
注意:
– created_at DESC 确保最新时间排第一
– 补上 id DESC 是为了当时间相同时结果可重现(避免优化器随机选行)
– 别在外部查询里再写 GROUP BY,会干扰窗口函数行为
和 DISTINCT、GROUP BY 的本质区别在哪?
DISTINCT 是基于所有 SELECT 字段值完全一致才合并;GROUP BY 必须配聚合函数,无法直接返回原始行。而 ROW_NUMBER() 方案可以:
– 完整保留你想留下的那行所有字段
– 支持复杂排序逻辑(比如“优先选 status=1 的,其次按时间”)
– 可扩展为取第2条、倒数第1条等灵活需求
性能提示:
– PARTITION BY + ORDER BY 字段最好有联合索引,例如 (user_id, created_at, id)
– 在大数据量下,子查询套一层可能比 CTE 稍快(取决于数据库版本,PostgreSQL 12+、MySQL 8.0+ 差异已不大)
– 如果只是简单去重且字段少,DISTINCT ON (user_id) ... ORDER BY user_id, created_at DESC(PostgreSQL)更简洁
最容易被忽略的是:窗口函数执行顺序在 WHERE 和 GROUP BY 之后、ORDER BY 之前。所以你不能在主查询的 WHERE 里直接引用 ROW_NUMBER(),必须用子查询或 CTE 包一层——这个嵌套不是可选项,是 SQL 执行模型决定的硬约束。










