rank()跳号、dense_rank()不跳号、row_number()强制唯一编号;分组排名必须用PARTITION BY,否则为全表排名;ORDER BY需加唯一字段保稳定;WHERE不能直接过滤窗口函数结果。

rank()、dense_rank()、row_number() 三者区别在哪
分组排名最常踩的坑,是没搞清这三个函数的行为差异。它们都按 ORDER BY 排序,但对相同值的处理完全不同:
-
rank():跳号。比如两个并列第1,下一个是第3 -
dense_rank():不跳号。两个并列第1,下一个是第2 -
row_number():强制唯一编号。哪怕值相同,也按出现顺序给 1、2、3…
选哪个取决于业务逻辑。销售榜单要体现“并列”,用 dense_rank();生成唯一流水号,必须用 row_number()。
必须搭配 PARTITION BY 才算“分组”排名
只写 ORDER BY 是全表排名;加 PARTITION BY 才是按组独立计算。漏掉它,结果完全不对。
常见错误写法:SELECT name, score, rank() OVER (ORDER BY score DESC) —— 这是全公司统一排名,不是各部门内部排名。
正确写法示例(按部门分组):
SELECT dept, name, score, dense_rank() OVER (PARTITION BY dept ORDER BY score DESC) AS rank_in_dept FROM employees;
PARTITION BY 的字段必须来自查询的 SELECT 列或 FROM 表中,不能是计算列(如 CONCAT(first_name, last_name)),否则 MySQL 8.0+ 会报错 Window 'w' uses an undefined window name or an illegal reference。
ORDER BY 中多个字段会影响排名稳定性
如果排序字段有重复值(比如多人同分),ORDER BY score DESC 不保证每次执行顺序一致,导致 row_number() 结果波动。
解决办法是补一个唯一字段做“决胜字段”:
- 优先用主键:
ORDER BY score DESC, id ASC - 没有主键时,可用时间戳:
ORDER BY score DESC, created_at ASC, id ASC
注意:MySQL 对 NULL 的默认排序是 NULLS FIRST,但窗口函数不支持显式写 NULLS LAST。若需把 NULL 排最后,得用 IFNULL(score, -999999) 之类兜底。
WHERE 不能直接过滤窗口函数结果
想查“每个部门前3名”,不能写 WHERE rank_in_dept ,因为窗口函数在 <code>WHERE 之后执行,会报错 Unknown column 'rank_in_dept' in 'where clause'。
必须用子查询或 CTE:
WITH ranked AS (
SELECT dept, name, score,
dense_rank() OVER (PARTITION BY dept ORDER BY score DESC) AS rnk
FROM employees
)
SELECT dept, name, score FROM ranked WHERE rnk <= 3;
CTE 在 MySQL 8.0+ 可用;低版本只能套一层子查询。别图省事写成 HAVING,那会触发聚合错误。
真正麻烦的是大数据量下的性能——PARTITION BY 字段如果没有索引,排序开销会陡增。务必确保 dept 和 score 组合有联合索引,顺序按 PARTITION BY 在前、ORDER BY 在后。


















