用ROW_NUMBER()取每组最新/最大记录最稳妥,因MAX()仅返回值而非整行;正确写法是先用ROW_NUMBER() OVER(PARTITION BY dept ORDER BY score DESC, id ASC)编号,再WHERE rn=1筛选。

用窗口函数 ROW_NUMBER() 取每组最新/最大记录最稳
直接在 GROUP BY 后用 MAX() 只能拿到值,拿不到整行数据。真要查“每组中 score 最高的那条完整记录”,必须借助窗口函数。推荐用 ROW_NUMBER() 而不是 RANK() 或 DENSE_RANK(),除非你明确需要处理并列情况。
常见错误是写成:SELECT *, MAX(score) OVER (PARTITION BY dept) FROM emp —— 这只是把最大值“广播”到每行,没过滤。
- 正确做法:先用
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY score DESC)给每组按分数降序编号 - 再在外层
WHERE rn = 1筛出每组第一行 - 注意
ORDER BY里可加多个字段防随机排序,比如ORDER BY score DESC, id ASC
MySQL 8.0+ 和 PostgreSQL 支持标准写法,老版本得绕
如果你用的是 MySQL 5.7 或更早版本,不支持窗口函数,就得用自连接或相关子查询,性能差、易出错。
典型自连接写法(假设找每部门最高分员工):
SELECT e1.* FROM emp e1 LEFT JOIN emp e2 ON e1.dept = e2.dept AND e1.score < e2.score WHERE e2.id IS NULL;
这个逻辑是:“找不到同部门分数比它还高的记录”的人,就是最高分者。但要注意:e2.score 为 NULL 或有重复最大值时,可能漏掉部分记录;LEFT JOIN 条件里不能写 e1.score ,否则会全匹配。
GROUP BY + JOIN 关联主表也能做,但容易漏数据
有人会先用 GROUP BY 拿每组最大值,再和原表 JOIN 回去。这看似直观,但只要存在多个相同最大值,就会返回多条记录——而你可能只想要一条(比如取 id 最小的那个)。
示例:
SELECT e.* FROM emp e INNER JOIN ( SELECT dept, MAX(score) AS max_score FROM emp GROUP BY dept ) t ON e.dept = t.dept AND e.score = t.max_score;
- 如果某部门两条记录
score = 95,这条语句会返回两条,不是“每组一条” - 若想强制一条,得在子查询里加
MIN(id)或用ROW_NUMBER()预处理 - 索引建议:给
(dept, score)建联合索引,加速GROUP BY和JOIN
PostgreSQL 用 DISTINCT ON 是最简解法
PostgreSQL 提供了非标准但极其简洁的 DISTINCT ON,适合这类“每组取首行”场景,比窗口函数写起来少一半代码。
写法:
SELECT DISTINCT ON (dept) * FROM emp ORDER BY dept, score DESC, id;
关键点:
-
DISTINCT ON的字段必须出现在ORDER BY开头,否则报错 -
ORDER BY后续字段决定“哪一行被选中”,比如这里score DESC保证最高分优先,id保证相同时取最小id - 仅限 PostgreSQL,别在 MySQL 或 SQL Server 里试,会语法错误
窗口函数通用性强,DISTINCT ON 写得快但锁定数据库。真正上线前,得看执行计划里有没有走索引,尤其当表超过百万行时,ORDER BY 的代价可能比想象中高。

















