RANK() 和 DENSE_RANK() 不能在 GROUP BY 后直接使用,因为它们是窗口函数,执行阶段晚于 GROUP BY,需先通过 CTE 或子查询完成聚合,再对外层结果排名。

RANK() 和 DENSE_RANK() 不能直接用在 GROUP BY 后的同一层查询里——你得先把聚合结果算出来,再把它当“新表”去排名。
为什么不能在 GROUP BY 后直接套 RANK()?
因为 RANK() 是窗口函数,不是聚合函数;而 GROUP BY 的执行阶段在 SELECT 之前,窗口函数却在 SELECT 阶段才计算。两者语义冲突,数据库会报错或返回意外结果。
-
ERROR: window function requires an ORDER BY clause(漏写OVER (ORDER BY ...)) -
column "avg_salary" does not exist(想在OVER里直接写AVG(salary),但窗口函数里不能嵌套聚合) - 全表排名而非分组内排名(漏了
PARTITION BY)
正确做法:先 CTE 聚合,再窗口排名
把 GROUP BY 结果封装进 CTE 或子查询,外层再用 RANK() OVER (...)。这是最稳、最易读的方式。
- CTE 必须定义别名字段,比如
avg_salary,否则外层无法引用 -
OVER (ORDER BY avg_salary DESC)中的avg_salary是 CTE 里的别名,不是原始表字段 - 如果要“每组内排名”,才加
PARTITION BY dept;这里是“对所有部门的平均薪资统一排名”,所以不加 - NULL 值默认排最后,若需前置,写成
ORDER BY avg_salary DESC NULLS FIRST(PostgreSQL/Oracle 支持;MySQL 不支持NULLS FIRST语法)
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,
DENSE_RANK() OVER (ORDER BY avg_salary DESC) AS dense_rank_by_avg
FROM dept_avg;
遇到除零或空值时怎么保排名逻辑不崩?
聚合后字段可能为 NULL 或 0(比如某部门没数据),直接参与 ORDER BY 会导致排名错位或不可控。这不是排名函数的问题,而是上游数据质量的问题。
- 用
COALESCE(avg_salary, 0)把 NULL 统一转成 0,避免排序异常 - 若业务上
0和NULL意义不同,建议提前在 CTE 里过滤:WHERE salary IS NOT NULL - 别在
OVER子句里写条件表达式,如ORDER BY CASE WHEN avg_salary > 0 THEN avg_salary END——这会让 NULL 全挤在开头或结尾,且 MySQL 不允许这种写法
性能与兼容性要注意什么?
窗口函数本身不慢,但数据量大 + 分区多时,PARTITION BY 会触发多次内部排序。而二次排名通常不需要分区,所以重点在“聚合结果集大小”。
- MySQL 8.0+、PostgreSQL、SQL Server、Oracle 均原生支持;SQLite 需 3.25+ 且编译时启用窗口函数
- 聚合后只剩几十行?放心用;如果 CTE 输出上万行,检查是否漏了
WHERE过滤(比如只看活跃部门) - 索引对聚合阶段有效,但对窗口排名无效——
(dept)索引能加速GROUP BY dept,但不影响ORDER BY avg_salary的排序速度
真正容易被忽略的是:排名依据必须是确定性值。如果 avg_salary 是浮点数,不同精度下可能导致相同值被拆成两个排名;必要时用 ROUND(avg_salary, 2) 统一精度再排名。










