首页 > 数据库 >如何使用SQL窗口函数ROW_NUMBER进行分组去重?

如何使用SQL窗口函数ROW_NUMBER进行分组去重?

来源:互联网 2026-07-12 08:38:00

行号函数仅分配行序号,不能直接去重。需通过子查询或公用表表达式过滤行号为1的行,实现分组内去重。若排序字段有重复,应加入唯一键以保证排序稳定性。还需注意不同数据库对空值排序的差异及版本兼容性。

SQL中的ROW_NUMBER()函数常被误会成去重利器,但它其实只负责给行打序号——真正要删除重复行,还得靠外层筛选,比如 WHERE rn = 1。这个道理初看简单,实操时却容易踩坑,特别是排序不稳定或数据库兼容性问题。下面就把这块讲透。

如何使用SQL窗口函数ROW_NUMBER进行分组去重?

长期稳定更新的攒劲资源: >>>点此立即查看<<<

一句话结论: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 LASTNULLS 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_at
FROM ranked
WHERE rn = 1;

和 DISTINCT、GROUP BY 的关键区别

DISTINCT 是基于整行值去重,GROUP BY 必须搭配聚合函数。而 ROW_NUMBER() 方案的好处是:能「保留原始行的任意字段」——只要排序逻辑设计合理,就能挑出你想要的那条完整记录。

举个例子:按 user_id 分组,取 updated_at 最新的那条,同时还要拿到它的 statussourceid 等字段。GROUP BY 做不到这点(除非把所有字段都写进 GROUP BY,那就失去了去重的意义)。

性能方面:ROW_NUMBER() 需要排序,大数据量时务必关注 PARTITION BYORDER BY 字段是否有索引。MySQL 8.0+、PostgreSQL、SQL Server 都支持该函数,但 SQLite 目前(3.45 版本)仍不支持窗口函数。

兼容性陷阱:某些还在用旧版 MySQL 的同学可能误以为开窗函数能用,结果报错 ERROR 1064: You ha ve an error in your SQL syntax ——其实就是版本太低,升级到 8.0 以上才能用。

容易被忽略的排序稳定性问题

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 最大的那条,结果稳定可预期。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备2026025700号-3 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。