RANK()遇并列跳号(如1,1,3),DENSE_RANK()连续编号(如1,1,2);前者影响Top N结果行数,后者保证分档数量稳定;二者均需OVER子句,不可与GROUP BY混用,须嵌套子查询实现聚合后排名。

为什么 RANK() 和 DENSE_RANK() 排名结果不一样
根本区别在「相同值是否占用后续排名名额」:RANK() 遇到并列会跳号(比如两个第1名后直接是第3名),DENSE_RANK() 则连续编号(两个第1名后是第2名)。这不光是显示差异,会影响你后续用排名做阈值过滤(如“取前10名”)时的实际返回行数。
常见错误现象:用 RANK() 算销售Top 10,结果只返回8行——因为第3、4名并列,跳到了第6名,导致实际凑不够10个不同排名值。
- 适用场景:
RANK()更适合强调「名次层级感」(如竞赛颁奖,两个冠军后就是季军);DENSE_RANK()更适合需要稳定数量切片(如按销售额分五档,每档必须有数据) - 两者都必须配合
OVER (ORDER BY ...)使用,且不能单独出现在 WHERE 或 GROUP BY 中 - 若需分区后排名(如“每个部门内销售额排名”),加上
PARTITION BY department
GROUP BY 后不能直接用 RANK() —— 常见报错和绕过方式
典型错误:SELECT department, RANK() OVER (ORDER BY SUM(sales)) FROM sales GROUP BY department 会报错,因为窗口函数不能和聚合混用在同一层 SELECT 中。
正确做法是把聚合和排名拆成两层:
SELECT department, total_sales, RANK() OVER (ORDER BY total_sales DESC) AS rk FROM ( SELECT department, SUM(sales) AS total_sales FROM sales GROUP BY department ) t;
- 别试图在子查询里加
WHERE过滤排名结果(如WHERE rk )——窗口函数在 WHERE 之后执行,必须用外层再套一层或用 CTE - MySQL 8.0+、PostgreSQL、SQL Server 2017+、Oracle 都支持;SQLite 和旧版 MySQL 不支持窗口函数
- 如果数据库不支持,得用自连接或变量模拟(性能差、逻辑易错,不推荐)
ORDER BY 中 NULL 值怎么影响 RANK() 结果
RANK() 默认把 NULL 当作最大值处理(即排在最后),但不同数据库行为不一致:PostgreSQL 默认 NULLS LAST,而 SQL Server 可能按 NULLS FIRST。一旦字段含空值,排名顺序可能和预期不符。
- 显式声明更安全:
ORDER BY amount DESC NULLS LAST(PostgreSQL/Oracle)或ORDER BY ISNULL(amount, 0) DESC(SQL Server) - 如果业务上
NULL表示“未发生”,应提前用COALESCE(amount, 0)统一转换,避免排名逻辑被隐式规则干扰 - 测试时务必用含
NULL的样例数据跑一遍,尤其当排序字段是可空的外键或计算字段
用 RANK() 实现「每组取 Top N」时最易忽略的细节
写 WHERE rk 看似简单,但若同一组内第3名有3人并列,<code>RANK() 会给出 1,1,3,3,3,6…,最终返回5行而非3行;DENSE_RANK() 是 1,1,2,2,2,3…,返回6行。二者都不等于“严格取前3个记录”。
- 要真正限制条数,得结合
ROW_NUMBER()(它不并列,强制唯一序号) - 如果必须用
RANK()且接受并列,就明确文档化这个行为:“取排名≤3的所有记录,可能超过3行” - 分区场景下,
PARTITION BY dept ORDER BY sales DESC的ORDER BY必须包含足够区分度的次级字段(如ORDER BY sales DESC, id ASC),否则同组同销售额的行排名完全随机(尤其在无主键或索引时)
真正难的不是写对语法,而是想清楚「你要的是名次位置,还是固定数量,还是业务意义上的‘前三’」——这三个目标对应三种函数选择,混用就会漏数据或超量。

















