RANK() 和 DENSE_RANK() 不能在 GROUP BY 后直接使用,因窗口函数在 SELECT 阶段执行,而 GROUP BY 已聚合数据、丢失行级细节;正确做法是先开窗再聚合,或用子查询/CTE对聚合结果二次开窗。

为什么 RANK() 和 DENSE_RANK() 在 GROUP BY 后不能直接用
因为 SQL 执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT,而窗口函数(如 RANK())是在 SELECT 阶段计算的,此时分组已经完成,但“每组内排序”需要的是未聚合前的行级数据。所以你不能先 GROUP BY 再对结果集直接套 RANK()——那会报错或逻辑错乱。
常见错误现象:ERROR: window function calls require an OVER clause 或排名全为 1(因聚合后只剩一行)。
- 正确做法是:先用窗口函数在原始粒度上排名,再按需聚合(比如取每组 top 3)
- 如果必须“对聚合结果排名”,得把聚合结果作为子查询/CTE,再在其上开窗
-
RANK()和DENSE_RANK()本身不感知GROUP BY,只认OVER (PARTITION BY ... ORDER BY ...)
RANK() vs DENSE_RANK() 的断层处理差异
两者都支持 PARTITION BY 实现“每组独立排名”,但遇到相同值时行为不同:前者跳号,后者连续。
比如某组分数为 [95, 95, 87, 87, 76]:
-
RANK()给出 [1, 1, 3, 3, 5] —— 第二个 95 还是第 1 名,但下一个名次直接跳到 3 -
DENSE_RANK()给出 [1, 1, 2, 2, 3] —— 更适合“并列算同一档”的业务场景(如奖项分级) - 性能上无差异,都是 O(n log n),但
RANK()在后续做 top-N 过滤时容易漏数据(比如rank 会漏掉第 4 名实际是并列第 3 的情况)
在分组聚合后对结果排名的写法(CTE + 窗口函数)
想对“每个部门的平均薪资”从高到低排总名次?不能在 GROUP BY dept 的 SELECT 里直接写 RANK() OVER (ORDER BY AVG(salary)),得拆成两步。
WITH dept_avg AS ( SELECT dept, AVG(salary) AS avg_salary FROM employees GROUP BY dept ) SELECT dept, avg_salary, RANK() OVER (ORDER BY avg_salary DESC) AS rank_by_avg FROM dept_avg;
注意点:
-
OVER ()里不能出现聚合函数(如AVG(salary)),必须先聚合完再进窗口 - 如果要“每组内按某指标排名”,
PARTITION BY放在 CTE 外层更安全(避免误 partition 原始表) - MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 3.25+ 也支持,但旧版不支持窗口函数
ORDER BY 中 NULL 值怎么影响排名
RANK() 和 DENSE_RANK() 默认把 NULL 当最大值(即排最后),和 ORDER BY col DESC 一致。但如果你的业务要求 NULL 排最前,必须显式声明:
- 写成
ORDER BY col DESC NULLS FIRST(PostgreSQL、Oracle、SQL Server 2022+ 支持) - MySQL 不支持
NULLS FIRST/LAST,得用ORDER BY ISNULL(col), col DESC - 否则会出现:明明有
NULL薪资,却排在了第 1 名(因为被当最大值),而你完全没意识到
这个细节在报表类需求里特别容易被忽略,尤其是字段允许为空又参与排名时。

















