用left join自连接找“没有更高分”的记录:select s1.category, s1.name, s1.score from student s1 left join student s2 on s1.category = s2.category and s1.score
GROUP BY 后怎么拿到每个分类的最高分学生?
直接
GROUP BY category加MAX(score)只能拿到最高分,拿不到对应的学生姓名——这是初学者最常卡住的地方。MySQL 不像某些数据库支持窗口函数(如ROW_NUMBER())开箱即用,得靠自连接、子查询或LEFT JOIN配合比较逻辑来“揪出”那条完整记录。用 LEFT JOIN 自连接找“没有更高分”的那条记录
核心思路:对每条记录,检查同分类下是否存在 score 更高的记录;如果不存在,说明它就是该分类最高分者。这个逻辑天然适合
LEFT JOIN+IS NULL判断。示例语句:
SELECT s1.category, s1.name, s1.score FROM student s1 LEFT JOIN student s2 ON s1.category = s2.category AND s1.score <p>注意点:</p>
- 连接条件里是
s1.score (不是 ≤),否则相等分数会互相匹配导致误筛- 如果有多个学生同属一个分类且分数并列最高,这条语句会全部返回——这是合理行为,不是 bug
- 务必给
(category, score)建联合索引,否则性能会随数据量陡增用相关子查询 + IN 配合 MAX()(简单但有陷阱)
写法直观:
WHERE (category, score) IN (SELECT category, MAX(score) ... GROUP BY category)。但要注意 MySQL 5.7 默认开启ONLY_FULL_GROUP_BY,子查询里若只SELECT category, MAX(score)而不GROUP BY category会报错。安全写法:
SELECT category, name, score FROM student WHERE (category, score) IN ( SELECT category, MAX(score) FROM student GROUP BY category );潜在问题:
- 如果
score是浮点数,IN比较可能因精度问题漏匹配,建议转成DECIMAL或改用JOIN方式- MySQL 8.0+ 支持行构造器比较,5.7 也支持但需确认版本;低版本只能拆成两个独立子查询
- 当存在大量重复最高分时,性能不如
LEFT JOIN稳定MySQL 8.0+ 推荐用窗口函数 ROW_NUMBER()
如果环境允许升级或已是 8.0+,这是最清晰、可读性最强的方式。用
ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC)给每组分数排位,取rn = 1即可。SELECT category, name, score FROM ( SELECT category, name, score, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn FROM student ) t WHERE rn = 1;注意区别:
ROW_NUMBER()强制去重编号(并列时随机排 1/2),要保留所有并列最高分,得换RANK()或DENSE_RANK()- 窗口函数不能出现在
WHERE子句中,必须套一层子查询或 CTE- ORDER BY 里建议加上次要排序字段(如
name),避免结果不稳定真正麻烦的不是语法,而是面对并列最高分、NULL 值、字符集排序规则差异时,不同方法的行为会悄然变化。上线前一定用真实分布数据跑一遍,别只测“理想 case”。












