应根据并列处理需求选择:rank()并列跳号,dense_rank()并列不跳号,row_number()严格顺序编号;窗口函数须在聚合后使用,需通过CTE或子查询实现。

rank()、dense_rank() 和 row_number() 三者怎么选
分组后排名,核心是窗口函数的排序逻辑差异。三者都支持 PARTITION BY 分组,但并列处理方式不同:
-
rank():并列跳号,比如两个第1名,下一个就是第3名 -
dense_rank():并列不跳号,两个第1名后是第2名 -
row_number():严格按顺序编号,哪怕值相同也会强加先后(依赖 ORDER BY 子句的稳定性)
实际业务中,销售额并列时要“同名次”,用 dense_rank() 更符合报表习惯;若需唯一序号(如分页取第N条),必须用 row_number()。
在 GROUP BY 后直接套窗口函数会报错吗
会。SQL 执行顺序是 GROUP BY → HAVING → SELECT → WINDOW,但窗口函数不能出现在 GROUP BY 子句之后的同一层级 SELECT 中——除非你把它放在子查询或 CTE 里。
- 错误写法:
SELECT dept, SUM(salary), rank() OVER (PARTITION BY dept ORDER BY SUM(salary)) FROM emp GROUP BY dept;(多数数据库报错) - 正确做法:先聚合,再对聚合结果开窗。用 CTE 最清晰:
WITH dept_sum AS ( SELECT dept, SUM(salary) AS total_salary FROM emp GROUP BY dept ) SELECT dept, total_salary, dense_rank() OVER (ORDER BY total_salary DESC) AS rk FROM dept_sum;
ORDER BY 在窗口函数里写什么才有效
这里必须是聚合后的字段,且不能混用未聚合列。常见错误是写成 ORDER BY salary 或 ORDER BY emp.name——这些在聚合后已不存在或不唯一。
- 正确:用
ORDER BY total_salary、ORDER BY COUNT(*)等聚合别名或表达式 - 注意 NULL:默认升序,NULL 排最前;想让 NULL 排最后,加
NULLS LAST(PostgreSQL/Oracle 支持;MySQL 8.0+ 不支持该语法,需用IFNULL或条件转换) - 多级排序可写:
ORDER BY SUM(sales) DESC, COUNT(*) ASC
MySQL 8.0 和旧版本的兼容性陷阱
窗口函数只在 MySQL 8.0+ 原生支持。如果你在 5.7 或更早版本执行 rank() OVER,会直接报错 ERROR 1064。
- 替代方案只能靠变量模拟,但变量行为不稳定(尤其在 JOIN 或子查询中),且无法保证分组内独立排序
- 一个勉强可用的变量写法(仅限单线程、无并发、简单场景):
SELECT dept, total_salary, @rn := IF(@prev = dept, @rn + 1, 1) AS row_num, @prev := dept FROM (SELECT dept, SUM(salary) AS total_salary FROM emp GROUP BY dept ORDER BY dept, total_salary DESC) t, (SELECT @rn := 0, @prev := '') r; - 强烈建议升级或换用 CTE + 窗口函数——变量方案在复杂排序(如并列处理)上几乎不可靠
真正容易被忽略的是:窗口函数的 PARTITION BY 和外层 GROUP BY 的语义边界。一旦分组键和分区键不一致(比如按部门分组,却按城市分区),结果就完全错位,而且很难一眼发现。

















