SQL ROW_NUMBER、RANK、DENSE_RANK 应用

2次阅读

row_number() 未加 order by 时结果顺序不固定,属未定义行为,必须显式指定 order by 才能保证确定性;rank() 并列后跳号,dense_rank() 并列后不跳号;partition by 先分组,order by 再组内排序。

SQL ROW_NUMBER、RANK、DENSE_RANK 应用

ROW_NUMBER() 为什么每次结果顺序不固定?

没写 ORDER BYROW_NUMBER() 是未定义行为,数据库可能按任意物理顺序编号,不是“随机”,而是“不可靠”。你看到的顺序只是碰巧一致,换数据、换版本、加索引都可能变。

实操建议:

  • 必须在 OVER() 里写明 ORDER BY,哪怕只是按主键排序:ROW_NUMBER() OVER (ORDER BY id)
  • 如果业务上真不需要逻辑顺序,也得用确定性字段兜底,比如 ORDER BY id, created_at 避免重复值导致编号漂移
  • 别依赖执行计划或当前结果“看起来有序”——它不是保证,是巧合

RANK() 和 DENSE_RANK() 差在哪?看并列后怎么跳号

三个人分数 95、95、87,RANK() 给出 1、1、3(跳过 2),DENSE_RANK() 给出 1、1、2(不跳)。关键区别就在“并列之后下一个名次是否补空位”。

常见错误现象:

  • RANK() 做 Top N 排行时漏掉第 2 名(因为被跳过了),实际想要的是连续名次就该换 DENSE_RANK()
  • 导出报表给业务方看“第几档”时用了 RANK(),结果出现“第1档、第1档、第3档”,对方问“第2档去哪了?”

使用场景提示:

  • RANK():适合强调“名次层级”,比如奥运奖牌榜(两个金牌=并列第一,银牌永远是第三名)
  • DENSE_RANK():适合做分档、分组编号,比如把销售额分成 1–5 档,不能缺档

窗口函数里 PARTITION BY 和 ORDER BY 谁先生效?

PARTITION BY 先切块,ORDER BY 再在每块内排序编号。顺序不能颠倒,也不能只留一个——漏掉 ORDER BY 会报错或返回无意义序号(如 postgresql 直接拒绝)。

参数差异与坑:

  • PARTITION BY dept_id 后,每个部门独立编号,但若没 ORDER BY salary DESC,同一部门里谁排第几完全不确定
  • 想按部门分组 + 按薪资降序排名?写成 OVER (PARTITION BY dept_id ORDER BY salary DESC),别反过来
  • mysql 8.0+、PostgreSQL、SQL Server 都支持;但旧版 MySQL(ERROR 1064

用 ROW_NUMBER() 去重为什么有时失效?

ROW_NUMBER() OVER (PARTITION BY a, b ORDER BY c) = 1 筛最新记录,看似稳妥,但容易栽在 ORDER BY 字段有 NULL 上——不同数据库对 NULLS FIRST/LAST 默认处理不同(PostgreSQL 默认 NULLS LAST,Oracle 默认 NULLS FIRST)。

实操建议:

  • 显式声明 NULLS LASTNULLS FIRST,比如:ORDER BY updated_at DESC NULLS LAST
  • 如果 updated_at 可能全为 NULL,再加个二级排序保底:ORDER BY updated_at DESC NULLS LAST, id DESC
  • 别假设“时间越新越靠前”就一定成立——没显式控制 NULL 行为,ROW_NUMBER() 可能把 NULL 当最大值排第一

复杂点在于:同一个 SQL,在开发环境(PostgreSQL)跑对了,上线到生产(Oracle)就选错记录。这种隐式行为差异,最容易被忽略。

text=ZqhQzanResources