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

ROW_NUMBER() 为什么每次结果顺序不固定?
没写 ORDER BY 的 ROW_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 LAST或NULLS 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)就选错记录。这种隐式行为差异,最容易被忽略。