应使用窗口函数row_number()配合子查询获取每组最大值对应完整记录,因group by加max()仅返回最大值无法绑定整行;需在partition by分组内按目标字段降序排序并加唯一字段确保稳定性,再外层筛选rn=1。

用窗口函数 ROW_NUMBER() 配合子查询最稳妥
直接 GROUP BY 加 MAX() 只能拿到最大值本身,拿不到对应那行的其他字段。真正要“取整行”,得先给每组内按目标字段排序编号,再筛出序号为 1 的记录。
常见错误是写成 SELECT *, MAX(score) FROM students GROUP BY class——这在大多数数据库(如 PostgreSQL、SQL Server)会报错,MySQL 5.7+ 严格模式下也拒绝执行,因为 * 和非聚合字段无法与 GROUP BY 对齐。
- 推荐写法:用
ROW_NUMBER() OVER (PARTITION BY class ORDER BY score DESC)给每班成绩从高到低编号 - 外层查
rn = 1的行,就能拿到每班最高分那条完整记录 - 注意
ORDER BY里加id或其他唯一字段可解决并列时结果不确定的问题,例如ORDER BY score DESC, id ASC
MySQL 8.0+ 和 PostgreSQL 支持 SELECT ... FROM t LATERAL (SELECT ...) 写法
如果每组要取的是关联表中最新/最高的一条(比如订单里每个用户最新一笔支付),LATERAL 比嵌套窗口函数更直观,且避免重复计算。
典型场景:查每个用户的最新一条订单记录。
- PostgreSQL 示例:
SELECT u.*, o.* FROM users u LEFT JOIN LATERAL (SELECT * FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1) o ON true - MySQL 8.0+ 同样支持
LATERAL,但注意必须用JOIN语法,不能用逗号隐式连接 - 性能上,若
orders(user_id, created_at)有联合索引,LATERAL通常比窗口函数 + 全局排序更快
NOT EXISTS 子查询适合小数据量或需兼容老版本 MySQL
不依赖窗口函数或 LATERAL,纯 SQL 标准写法,在 MySQL 5.6、SQLite 等环境也能跑。
核心思路:找不出“同组里有更大值”的记录,就是最大值所在行。
- 示例:
SELECT * FROM students s1 WHERE NOT EXISTS (SELECT 1 FROM students s2 WHERE s2.class = s1.class AND s2.score > s1.score) - 缺点明显:当存在并列最大值时,会返回多行;且没有索引时性能差,
s2表会被扫描多次 - 务必确保
(class, score)有复合索引,否则执行计划大概率走全表嵌套扫描
别忽略并列情况和 NULL 值处理
真实数据里,同一组常有多个相同最大值。用 ROW_NUMBER() 只会返回其中一行,而 RANK() 或 DENSE_RANK() 会把并列的都标为 1,结果行数可能变多。
另外,如果用于排序的字段含 NULL,不同数据库行为不一致:PostgreSQL 默认 NULLS LAST,MySQL 把 NULL 当最小值。显式写清楚更安全。
- 想让
NULL排最后:加NULLS LAST(PostgreSQL、Oracle 支持) - MySQL 没这个语法,改用
ORDER BY score IS NULL, score DESC - 如果业务逻辑要求排除
NULL,记得在WHERE里先过滤,比如WHERE score IS NOT NULL
实际写的时候,先确认数据库版本和并列需求,再选函数;窗口函数虽好,但旧版 MySQL 或某些 OLAP 引擎不支持,硬套会报错。











