GROUP BY 不能直接排名,必须用窗口函数;正确写法是去掉 GROUP BY,用 PARTITION BY department 配合 ROW_NUMBER()、RANK() 或 DENSE_RANK() 实现部门内薪资排序。

GROUP BY 不能直接做排名,得靠窗口函数
GROUP BY 本身只做分组聚合,不产生行内序号或排名。想在每个部门内按薪资排第1、第2、第3……必须用 ROW_NUMBER()、RANK() 或 DENSE_RANK() 这类窗口函数,配合 PARTITION BY department 划分部门边界。
常见错误是写成:SELECT department, name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) FROM emp GROUP BY department —— 这会报错,因为 GROUP BY 和未聚合的 name、salary 冲突,且 OVER 子句没指定分区。
- 正确写法是去掉
GROUP BY,改用PARTITION BY department - 排序方向要明确:升序用
ORDER BY salary ASC,降序用ORDER BY salary DESC - 如果同薪要并列(比如两个8000都算第1名),选
RANK();要连续编号(两个8000后下一个算第3名),选DENSE_RANK()
典型查询语句怎么写
假设表叫 emp,字段为 department、name、salary,查每个部门薪资前3名:
SELECT department, name, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM emp
WHERE rn <= 3;
注意:这个写法在多数数据库(PostgreSQL、SQL Server、Oracle)里可以直接运行;但 MySQL 8.0 之前不支持窗口函数,MySQL 5.7 及更早版本会报错 ERROR 1064。
- MySQL 5.7 必须用变量模拟排名,逻辑复杂且易出错
- PostgreSQL 和 SQL Server 对
PARTITION BY支持稳定,但ROW_NUMBER()不保证相同薪资的顺序,需加二级排序,如ORDER BY salary DESC, name ASC - WHERE 条件不能直接引用窗口函数别名
rn(除非套子查询),否则报错Unknown column 'rn'
为什么不能用 GROUP BY + MAX() 模拟排名
有人尝试用自连接或子查询统计“比当前薪资高的人数”来生成名次,例如:SELECT e1.department, e1.name, e1.salary, COUNT(e2.salary) + 1 AS rank FROM emp e1 LEFT JOIN emp e2 ON e1.department = e2.department AND e2.salary > e1.salary GROUP BY e1.id。
这种写法虽然能跑通,但实际问题很多:
- 性能差:N² 复杂度,数据量一过万就明显变慢
- 无法处理并列:两个相同最高薪会被同时算作第1名,但
COUNT结果仍是1,逻辑上没问题,可一旦加了WHERE rank 就可能漏掉并列第3名 - GROUP BY 字段必须包含所有非聚合列,否则 MySQL 5.7+ 严格模式下直接报错
Expression #2 of SELECT list is not in GROUP BY clause
真实业务中容易被忽略的细节
部门空值、薪资为 NULL、同一人多条记录——这些都会让排名结果偏离预期。
-
PARTITION BY department会把NULL当作一个独立分组,如果不想让空部门参与排名,加WHERE department IS NOT NULL -
ORDER BY salary DESC中,NULL默认排最前(MySQL)或最后(PostgreSQL),行为不一致,显式写成ORDER BY salary DESC NULLS LAST更安全(PostgreSQL 支持,MySQL 不支持) - 如果员工有多个薪资记录(比如调薪历史),必须先去重或取最新一条,否则同一人占多个名次
窗口函数不是银弹,它依赖数据库版本和具体实现,上线前务必在目标环境实测 NULL 和重复值场景。

















