当前位置: 首页
数据库
如何用SQL窗口函数ROW_NUMBER实现分组内去重

如何用SQL窗口函数ROW_NUMBER实现分组内去重

热心网友 时间:2026-07-08
转载

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

来源:https://www.php.cn/faq/2784640.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
配置Java应用支持Oracle数据库AD/Kerberos身份验证

配置Java应用支持Oracle数据库AD/Kerberos身份验证

配置Java应用支持OracleAD Kerberos需正确设置JVM的krb5 conf与jaas conf,JDBCURL添加oracle net authentication_services=(KERBEROS5)并与SPN严格匹配。AD用户需映射为Oracle企业用户并授予CREATESESSION权限。SPN注册和EUS映射是常见故障点。

时间:2026-07-08 07:04
Redis订阅者BGSAVE期间响应变慢:RDB持久化IO影响分析

Redis订阅者BGSAVE期间响应变慢:RDB持久化IO影响分析

Redis订阅者在BGSAVE期间响应变慢,源于fork阻塞、COW内存争抢及磁盘I O高负载三重叠加。通过将RDB迁移至从节点、禁用THP、调整swappiness并手动触发避开高峰,可有效降低对主服务的影响。

时间:2026-07-08 07:03
如何优雅捕获Node.js项目中的MongoDB写入异常

如何优雅捕获Node.js项目中的MongoDB写入异常

批量插入失败时不会抛出统一错误类型,需关注错误代码和写入错误数组。部分失败时可设置有序参数为假以获取写入错误数组。事务中写入失败会自动中止事务,提交事务不会执行。常见错误码11000为唯一键冲突,121为文档验证失败。

时间:2026-07-08 07:03
如何用SQL窗口函数ROW_NUMBER实现分组内去重

如何用SQL窗口函数ROW_NUMBER实现分组内去重

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

时间:2026-07-08 07:03
Spring Boot RedisTemplate无法直接注入?检查泛型声明是否正确

Spring Boot RedisTemplate无法直接注入?检查泛型声明是否正确

RedisTemplate注入失败多因泛型擦除导致类型不匹配,需保证配置类返回类型、注入字段类型及内部key value类型三者一致。避免使用无泛型或通配符形式,不同序列化策略应定义独立Bean并用@Qualifier区分。StringRedisTemplate为独立Bean,不可混用。

时间:2026-07-08 07:03
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜