最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如何使用SQL窗口函数ROW_NUMBER在分组内实现去重?
时间:2026-07-08 10:32:03 编辑:袖梨 来源:一聚教程网
ROW_NUMBER() 不能直接去重,仅能编号;真正去重需配合子查询或CTE筛选rn=1的行,核心是PARTITION BY定义重复组、ORDER BY确保排序稳定,否则结果不可控。
ROW_NUMBER() 不能直接去重,它只能给行打序号;真正去重得靠外部筛选,比如 WHERE rn = 1。
为什么 ROW_NUMBER() 本身不等于去重
它只是按指定排序规则给每一行分配一个唯一递增数字,哪怕两行数据完全相同,只要排序键(ORDER BY)能区分它们(或用了不确定排序),ROW_NUMBER() 也会分出 1、2、3…… 所以必须配合子查询或 CTE,再过滤出序号为 1 的行。
常见错误现象:SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) FROM orders; 这条语句只是加了一列序号,并没有减少行数。
标准写法:用 CTE 或子查询 + WHERE 筛选首行
这是最常用、也最易理解的模式。核心是先算出每组内的序号,再在外层只取 rn = 1 的记录。
- 如果想保留最新一条记录:用
ORDER BY updated_at DESC,然后取rn = 1 - 如果想保留最早一条:用
ORDER BY created_at ASC - 注意:如果排序字段有 NULL,不同数据库行为不同(如 PostgreSQL 把 NULL 排在最前,MySQL 可能默认最后),建议显式写
NULLS LAST或NULLS FIRST(PostgreSQL/Oracle 支持;MySQL 8.0+ 不支持该语法,需用IS NULL模拟)
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY updated_at DESC, id DESC ) AS rn FROM users)SELECT id, user_id, email, updated_atFROM rankedWHERE rn = 1;
和 DISTINCT、GROUP BY 的关键区别
DISTINCT 是基于整行值去重,GROUP BY 必须配聚合函数,而 ROW_NUMBER() 方案能「保留原始行的任意字段」——只要你排序逻辑合理,就能挑出你想要的那条完整记录。
- 例如:按
user_id分组,取updated_at最新那条,同时拿到它的status、source、id等所有字段,GROUP BY做不到这点(除非全写进 GROUP BY,那就失去去重意义) - 性能上:
ROW_NUMBER()需要排序,大数据量时注意PARTITION BY和ORDER BY字段是否有索引;MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 目前(3.45 仍不支持窗口函数) - 兼容性陷阱:某些旧版 MySQL 用户误以为开窗函数可用,结果报错
ERROR 1064: You have an error in your SQL syntax,其实是版本太低
容易被忽略的排序稳定性问题
当 PARTITION BY 内多行的排序字段值完全相同时(比如同个 user_id 下几条记录 updated_at 都是 '2024-01-01 00:00:00'),ROW_NUMBER() 的结果是不确定的——数据库可能每次返回不同“首行”。
解决办法只有一个:在 ORDER BY 里补一个能保证唯一性的字段(如主键 id),否则去重结果不可复现。
错误写法:ORDER BY updated_at DESC → 同时间戳下哪条被选中是随机的
正确写法:ORDER BY updated_at DESC, id DESC → 时间相同时,取 id 最大的那条,稳定可预期
相关文章
- 王者荣耀世界东方曜与鲁班大师配队培养方法 07-08
- 鸣潮2.5:优化了哪些方面 07-08
- 星际裂变新手前期如何探索废墟 07-08
- 这城有良田威仪属性解析 07-08
- 指尖无双治安亭任务挑选攻略 07-08
- 《心动小镇》烹饪玩法技巧分享 07-08