max()配合group by仅返回每组最大值本身,不带回原始行其他字段;需获取完整记录时应使用row_number()窗口函数实现top-n per group。

用 MAX() 配合 GROUP BY 取每组最大值,但要注意它只返回聚合结果
MAX() 和 GROUP BY 联用确实能算出每个分组的峰值(比如最高销售额、最大温度),但它只返回该峰值本身,不带回原始行的其他字段(如对应的时间、ID、姓名)。这是最常被误解的一点——很多人以为加了 GROUP BY 就能“取出整条峰值记录”,其实不能。
常见错误现象:SELECT id, name, MAX(score) FROM students GROUP BY class; —— 这条语句在 MySQL 5.7+ 严格模式下会报错 ERROR 1055,因为 id 和 name 不在 GROUP BY 中,也不在聚合函数里。
- 正确做法是:只选分组字段 + 聚合字段,例如
SELECT class, MAX(score) FROM students GROUP BY class; - 如果还要带出对应学生的
name,就得换思路(见下一条) - 注意不同数据库对非分组字段的容忍度不同:MySQL 5.7 之前可能“侥幸”返回某条随机记录,PostgreSQL 和 SQL Server 则直接拒绝执行
想取“峰值所在那行的完整数据”?别硬套 MAX() + GROUP BY
这时候 MAX() 本身不够用。你需要的是“每组中 score 最大的那条完整记录”,属于典型的 Top-N per group 问题。
推荐方案是用窗口函数 ROW_NUMBER()(兼容性较好,主流数据库都支持):
SELECT class, name, score
FROM (
SELECT class, name, score,
ROW_NUMBER() OVER (PARTITION BY class ORDER BY score DESC) AS rn
FROM students
) t
WHERE rn = 1;
说明:
-
PARTITION BY class对应分组逻辑,等价于GROUP BY class -
ORDER BY score DESC确保最高分排第一;若分数相同需稳定取值,建议追加唯一字段如id防止随机性:ORDER BY score DESC, id ASC - 用
ROW_NUMBER()而不是RANK()或DENSE_RANK(),是因为它保证每行编号唯一,避免多条并列第一时漏数据
MAX() 的实际适用场景和性能提示
MAX() + GROUP BY 真正适合的,是只需要统计值本身、不关心原始上下文的场合,比如日报表中的“各渠道当日最高下单金额”“各地区历史最高气温”。这类查询简单、高效、可读性强。
- 索引友好:如果
GROUP BY字段和MAX()字段上有联合索引(如(region, temperature)),数据库常能走索引扫描甚至索引跳跃扫描,避免全表排序 - 注意 NULL:
MAX()自动忽略 NULL 值;但如果整组全是 NULL,结果为 NULL,不是 0——业务上需要补默认值时得用COALESCE(MAX(x), 0) - 类型安全:对字符串字段用
MAX()是按字典序取“最大”,不是长度最长,例如MAX('apple', 'banana', 'cat')返回'cat',这点容易误判
MySQL 8.0+ 或 PostgreSQL 用户可以试试 SELECT ... LATERAL 或 WITH TIES
某些新版数据库提供了更贴近直觉的语法,但使用前务必确认版本和执行计划。
- PostgreSQL 支持
LIMIT 1 WITH TIES配合窗口函数,可简化写法 - MySQL 8.0+ 支持
LATERAL子查询,能实现类似“对每组执行一次子查询”的效果,但可读性和维护性不如标准窗口函数 - 不建议为省几行代码去强依赖这些特性——它们在跨库迁移或团队协作时容易成为隐形坑
真正难的从来不是写出峰值数字,而是搞清你要的是“一个数”,还是“那个数背后的人和事”。前者用 MAX + GROUP BY,后者绕不开窗口函数或自连接。










