不能直接用 group by 配合 max() 拿到完整行,因为 group by 是聚合操作,max() 仅返回标量值,其他非分组/非聚合字段语义未定义,mysql 5.7+ 默认报错,即使绕过也未必来自同一原始行。

为什么不能直接用 GROUP BY 配合 MAX() 拿到完整行
因为 GROUP BY 后,非分组字段如果没有聚合(如 MAX()、MIN()),在标准 SQL 中是语法错误;即使某些数据库(如 MySQL 5.7 前)允许,返回的其他字段值也是未定义的——可能来自任意一行,不一定是 MAX() 所在的那行。想拿“每个分组里 salary 最高的那条员工记录”,光靠 GROUP BY dept_id, MAX(salary) 不够,必须关联回原表或改用窗口函数。
用 RANK() 窗口函数选出每组最大值对应的所有行
RANK() 对相同值赋予相同排名,但会跳过后续名次(比如两个并列第1,下一个就是第3)。适合需要“并列且区分层级”的场景,比如排行榜。它能天然保留所有并列最高值的行:
SELECT * FROM (
SELECT *,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk
FROM employees
) t WHERE rk = 1;
注意点:
- 如果某部门有多个员工 salary 并列最高,
RANK()会让它们都得1,全被查出 -
ORDER BY salary DESC必须明确,否则排名无意义 - 若 salary 允许为
NULL,默认排在最前(因DESC),可加NULLS LAST(PostgreSQL/Oracle 支持)或用COALESCE(salary, -999999)控制
DENSE_RANK() 和 ROW_NUMBER() 的关键区别在哪
三者都常用于“取每组 top N”,但行为不同:
-
DENSE_RANK():并列不跳号(两个第1,下一个是第2),语义更接近“并列第几” -
ROW_NUMBER():强制唯一编号,相同值也会被随机打散成不同序号(如 1, 2, 3),无法保证取到真正的“最大值行” - 所以只要目标是“所有 salary 最大的行”,必须用
RANK()或DENSE_RANK();若只要一条(比如强制取 id 最小的那条),可在ORDER BY里追加二级排序:ORDER BY salary DESC, id ASC,再用ROW_NUMBER()
性能和兼容性要注意什么
窗口函数在 PostgreSQL、SQL Server、Oracle、MySQL 8.0+、SQLite 3.25+ 中都支持,但旧版 MySQL(5.7 及之前)不支持,必须用自连接或子查询模拟,性能差很多。
实际写法中容易漏掉的坑:
- 忘记
PARTITION BY,导致全表排名而非分组排名 -
ORDER BY写成升序(ASC)却想取最大值 - WHERE 条件写在窗口计算外层但没包裹子查询,导致语法错误(窗口函数不能直接出现在 WHERE 中)
- 大数据量时,
PARTITION BY + ORDER BY字段最好有复合索引,例如(dept_id, salary)
真正要稳定拿到“每组最大值所在完整行”,窗口函数是目前最清晰、可读性最强、也最不容易出错的方式——前提是你的数据库版本撑得住。










