
用 ROW_NUMBER() 找分组内最大值那行最稳
直接上结论:想精确拿到“每个分组里 score 最高的那条完整记录”,ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY score DESC) 是最可靠的选择。它不依赖唯一性,也不怕并列,还能控制相同值时的取舍逻辑(比如加 id 二次排序)。
常见错误是只用 MAX(score) 配合 GROUP BY,结果只能查出最大值,拿不到对应行的 name、created_at 等其他字段——这本质是聚合丢失明细的问题。
实操建议:
- 在子查询或 CTE 中计算
ROW_NUMBER(),外层筛选rn = 1 - 排序字段必须明确写全:
ORDER BY score DESC, id ASC,避免因执行计划变动导致结果不稳定 - 如果业务允许并列(比如两个最高分都算),改用
RANK()或DENSE_RANK(),但要注意它们会跳号或不跳号,rn = 1的语义就变了
用 JOIN + 子查询也能做,但容易漏数据或重复
老办法是先用子查询求每组最大值,再和原表 JOIN 匹配。看似直观,实际踩坑不少。
典型问题:
- 当一个分组有多个相同最大值(如两个
score = 95),JOIN会返回多行,但你可能只想要其中一条 - 如果子查询没加
DISTINCT或去重逻辑,而原表有NULL值参与比较,JOIN条件可能失效(NULL = NULL不成立) - 性能上,子查询需扫描两次表,大数据量时比窗口函数慢,且难利用索引(尤其
ORDER BY字段没索引时)
示例写法(仅限小数据、确定无并列):
SELECT t1.* FROM orders t1 JOIN ( SELECT customer_id, MAX(amount) AS max_amt FROM orders GROUP BY customer_id ) t2 ON t1.customer_id = t2.customer_id AND t1.amount = t2.max_amt;
FIRST_VALUE() 看似简单,其实不能直接拿来取整行
有人看到 FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY salary DESC) 就以为能一步到位,但这是错觉。
原因很实在:
-
FIRST_VALUE()是窗口函数,只返回指定列的值,不是整行;你想拿id、hire_date等其他字段,得对每个字段都套一遍,既啰嗦又无法保证来自同一行(排序依据稍有不同就错位) - 它不支持过滤,没法跳过
NULL或排除特定状态,灵活性远不如ROW_NUMBER()+ 外层WHERE - 某些数据库(如 MySQL 8.0 以前)不支持
FIRST_VALUE(),兼容性差
MySQL 5.7 或更老版本没窗口函数?用相关子查询兜底
如果真卡在 MySQL 5.7 这类不支持窗口函数的环境,别硬套 JOIN,用相关子查询更安全:
SELECT * FROM employees e1 WHERE salary = ( SELECT MAX(salary) FROM employees e2 WHERE e2.department = e1.department );
注意点:
- 这个写法默认接受并列结果,符合多数业务场景(比如“所有最高薪员工”)
- 如果只要一条,得在外层加
LIMIT 1,但这时已失去分组维度,不可取;正确做法是在子查询里加ORDER BY id LIMIT 1,但 MySQL 5.7 不支持子查询中用LIMIT,所以还是推荐升级或换用变量模拟 - 务必给
department和salary建联合索引,否则性能会断崖式下跌
真正麻烦的从来不是语法选哪个,而是没想清楚“并列时要不要全取”“NULL 值怎么参与排序”“历史数据里有没有脏值影响 MAX() 结果”——这些细节一动,所有写法都得重新评估。










