DENSE_RANK不能与GROUP BY混用,因GROUP BY压缩行而窗口函数需保留每行计算;正确做法是先用子查询或CTE计算DENSE_RANK,再在外层过滤排名。

SQL Server里DENSE_RANK不能直接和GROUP BY混用
你写DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC)时,如果还加了GROUP BY dept,SQL Server会报错:“窗口函数不能在包含GROUP BY子句的查询中使用”。这不是语法限制,而是语义冲突——GROUP BY要压缩行,而DENSE_RANK必须保留每行做组内计算。
所以别试图在同一个SELECT里既GROUP BY又开窗。真正要的是“分组后取TopN”,不是“分组再排名”。
- 先用子查询或CTE把
DENSE_RANK算出来,生成带排名的明细结果 - 再在外层过滤
ranking ,而不是聚合后再排名 - 若需聚合字段(如部门平均薪资),必须在子查询里一起SELECT出来,不能等到外层再
AVG()
为什么TopN必须用子查询套一层
SQL Server执行顺序是:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → TOP。而OVER属于SELECT阶段,但WHERE阶段还看不到窗口函数生成的列。所以直接写WHERE DENSE_RANK() OVER (...) 会报错:“无效的列名”。
正确做法是把窗口函数结果作为派生表的一列,再对外层筛选:
SELECT id, dept, salary, ranking
FROM (
SELECT id, dept, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS ranking
FROM employee
) t
WHERE ranking
- 子查询别名
t不能省,SQL Server强制要求 - 如果排序字段有重复值(比如两个18000),
DENSE_RANK会并列第1,下一个值是第2;而ROW_NUMBER会强制拆成1和2,选哪个取决于业务是否允许并列 - ORDER BY里建议加二级排序,比如
ORDER BY salary DESC, id ASC,避免相同salary时结果不稳定
想同时拿到TopN明细 + 分组聚合值怎么办
比如既要查“每个部门薪资前2的员工”,又要显示“该部门总人数”“部门平均薪资”。不能靠外层GROUP BY补,得把聚合值提前算好,和排名字段共存于同一子查询中。
用聚合窗口函数即可,它们和DENSE_RANK共享同一个OVER逻辑:
SELECT id, dept, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS ranking,
COUNT(*) OVER (PARTITION BY dept) AS dept_total_count,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg_salary
FROM employee;
-
COUNT(*) OVER (PARTITION BY dept)给出的是部门总人数,不是当前行数 - 所有
OVER子句里的PARTITION BY保持一致,否则聚合值和排名对不上组 - 如果只需要TopN行的聚合值(比如只显示前2人的部门平均薪),那就得先过滤再聚合,用CTE分两步走
ORDER BY里NULL值怎么处理才不踩坑
当排序字段含NULL(比如salary允许为空),默认情况下SQL Server把NULL排在最前面(ASC)或最后面(DESC),但这个行为可被NULLS FIRST/NULLS LAST覆盖——可惜SQL Server至今不支持这两个关键字(截至2026年9月仍为未实现特性)。
稳妥方案是显式控制NULL位置:
- 想让NULL排末尾(升序时):
ORDER BY ISNULL(salary, -9999999) ASC - 想让NULL排开头(降序时):
ORDER BY CASE WHEN salary IS NULL THEN 0 ELSE 1 END DESC, salary DESC - 否则可能发现“明明有高薪NULL记录,却没进Top2”,其实是它被排到第1位占了坑,但业务上并不想把它当有效数据
实际业务中,排名逻辑是否包含NULL必须明确约定,不能依赖默认行为。










