直接用 GROUP BY + MAX() 拿不到完整记录,因为 MAX() 是聚合函数,GROUP BY 时其他非分组字段值未定义;应使用 ROW_NUMBER() 窗口函数或自连接方案获取每组最高分的完整记录。

为什么直接用 GROUP BY + MAX() 拿不到完整记录
因为 MAX() 是聚合函数,配合 GROUP BY 时,其他非分组字段的值是未定义的(MySQL 5.7+ 默认拒绝这种写法,报错 ERROR 1055;旧版可能返回任意一行)。你想要的是“每组里 score 最高的那条完整记录”,不是“每组最高分是多少”。
用窗口函数 ROW_NUMBER() 最直观可靠
适用于 PostgreSQL、SQL Server、Oracle、MySQL 8.0+、SQLite 3.25+。核心思路:给每组按目标字段排序编号,取序号 = 1 的行。
- 写法示例(查每个
department中salary最高的人):SELECT id, name, department, salary FROM ( SELECT id, name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn FROM employees ) ranked WHERE rn = 1; -
PARTITION BY department表示按部门分组;ORDER BY salary DESC确保最高分排第一 - 用
ROW_NUMBER()而非RANK()或DENSE_RANK():避免并列时多条记录都被选中(除非你明确需要所有并列最大值) - 注意:如果同一部门有多个相同最高
salary,ROW_NUMBER()只返回其中一条(内部排序不稳定),需加二级排序(如ORDER BY salary DESC, id ASC)保证可重现
兼容老版本 MySQL(5.6/5.7)的自连接方案
不依赖窗口函数,但性能较差,且逻辑稍绕——本质是“找不出比它更大的同组记录”的那条。
- 关键条件:
NOT EXISTS或左连接后判断空:SELECT e1.id, e1.name, e1.department, e1.salary FROM employees e1 LEFT JOIN employees e2 ON e1.department = e2.department AND e1.salary < e2.salary WHERE e2.id IS NULL;
- 原理:对
e1的每条记录,尝试找同部门里salary更大的记录e2;找不到(e2.id IS NULL)说明e1就是该组最大值 - ⚠️ 注意:若存在
NULL值,e1.salary 判断会失效(<code>NULL参与比较结果为UNKNOWN),需提前过滤或用COALESCE处理 - 性能隐患:大数据量时,
LEFT JOIN易产生笛卡尔积,务必确保(department, salary)有联合索引
子查询关联时别漏掉去重和 NULL 安全
用相关子查询也能实现,但容易写出语义错误或性能陷阱。
- 常见错误写法(可能返回多条或漏数据):
SELECT * FROM employees e1 WHERE salary = (SELECT MAX(salary) FROM employees e2 WHERE e2.department = e1.department);
- 问题:如果某部门有多个员工并列最高分,这条会返回全部——这有时是需求,有时不是;更危险的是,若
salary允许为NULL,MAX()返回NULL,而= NULL永远不成立,导致该部门无结果 - 安全写法(显式处理 NULL,并控制返回条数):
SELECT * FROM employees e1 WHERE (e1.salary, e1.id) = ( SELECT MAX(e2.salary), MIN(e2.id) FROM employees e2 WHERE e2.department = e1.department );
这里用(salary, id)元组匹配,既保证唯一性,又规避了NULL比较问题
真正麻烦的不是语法,而是想清楚“并列时要几条”“NULL 怎么算”“性能能不能接受”——这些决定了该选窗口函数、自连接还是子查询。

















