该用 row_number 而不是 rank 时:需严格顺序唯一编号(如分页、取最新一条),因 row_number 不跳号不并列,而 rank 并列后跳号会导致漏数据。

什么时候该用 ROW_NUMBER 而不是 RANK
当你要严格按顺序给每行唯一编号(比如分页取第11–20条、取每个用户最新一条订单),必须用 ROW_NUMBER。它不跳号,不并列,纯粹按 ORDER BY 排序后硬生生从 1 开始标号。
常见错误是拿 RANK 做分页:如果两个用户下单时间相同,RANK 可能同时返回 1,下一行就直接跳到 3,导致 LIMIT 10 OFFSET 10 漏掉数据。
-
ROW_NUMBER():适合“取 Top N”“分页”“去重选最新” -
RANK():适合“成绩排名”,允许并列且要体现名次断层(如两个第1名,下一个就是第3名) -
DENSE_RANK():适合“梯队划分”,并列后不跳号(两个第1名,下一个是第2名)
NTILE(4) 划分四分位时的边界陷阱
NTILE 看似简单,但它的分组是“尽力平均”,不是按值切分。比如 10 行数据执行 NTILE(4),结果不是每组 2.5 行——SQL 会拆成 3、3、2、2 这样的分布,具体哪几行进哪组取决于排序顺序,不可预测。
业务上想按销售额把客户分成高/中高/中低/低四档,不能只靠 NTILE,得先算出 PERCENTILE_CONT(0.25) 等分位点,再用 CASE WHEN 手动判断。否则销售数据稍有倾斜,NTILE(4) 就会让“高”组塞进 30% 的人。
- 永远检查
COUNT(*)分组后各桶行数是否符合预期 - 如果需要等宽区间(如 0–25%、25–50%),改用
PERCENT_RANK()或聚合函数预计算阈值 -
NTILE在NULL值参与排序时行为因数据库而异(PostgreSQL 忽略,MySQL 可能报错)
窗口函数里 PARTITION BY 和 ORDER BY 的依赖关系
ORDER BY 在窗口函数中不是可选的——对 ROW_NUMBER、RANK、DENSE_RANK 来说,没写 ORDER BY 语法直接报错;对 NTILE 也是强制要求。但很多人以为加了 PARTITION BY 就可以省略 ORDER BY,其实不行。
更隐蔽的问题是:同一个 PARTITION BY 下,如果 ORDER BY 字段有重复值,ROW_NUMBER 仍会强行编号(顺序由底层存储或随机决定),而 RANK 和 DENSE_RANK 会并列。这意味着,仅靠 user_id 分组 + created_at 排序,若存在毫秒级相同时间,不同数据库可能给出不同编号结果。
- 关键排序字段尽量带唯一性兜底,例如
ORDER BY created_at, id -
PARTITION BY定义范围,ORDER BY定义顺序,二者缺一不可 - 在 PostgreSQL 中,
ORDER BY允许用表达式(如ORDER BY EXTRACT(YEAR FROM order_date)),但 MySQL 8.0+ 要求必须是列或别名
MySQL 8.0 vs PostgreSQL 对 DENSE_RANK 的 NULL 处理差异
如果你在 MySQL 里写 DENSE_RANK() OVER (ORDER BY score DESC),遇到 score IS NULL 的行,它们会被排在最末尾(即最低名次);但在 PostgreSQL 中,默认 NULLS LAST,行为一致。但一旦你显式写 ORDER BY score DESC NULLS FIRST,MySQL 直接报错不支持 NULLS FIRST/LAST 语法,PostgreSQL 则正常执行。
这意味着跨数据库迁移含窗口函数的报表 SQL 时,NULL 排序逻辑最容易出问题——表面跑通,结果错位。
- MySQL 8.0 不支持
NULLS FIRST/LAST,所有NULL统一归到排序末端 - PostgreSQL 和 SQL Server 支持,且默认策略可能不同(需查文档)
- 稳妥做法:提前用
CASE WHEN score IS NULL THEN -999999 ELSE score END替换,消除歧义










