group by + max() 无法获取整行数据,因max()仅返回字段最大值,其他非分组/非聚合字段在mysql 5.7+下会报错;需用关联子查询或窗口函数匹配最大值所在完整行。

为什么直接用 GROUP BY + MAX() 拿不到整行数据
因为 GROUP BY 是聚合操作,MAX() 只返回该分组下某个字段的值,其他字段(比如 id、name)若没参与分组或没加聚合函数,数据库会随机选一个——MySQL 5.7+ 默认报错,提示 SELECT list is not in GROUP BY。你真正想要的是“每个分组里 score 最高的那条完整记录”,不是“每个分组的最高分是多少”。
用关联子查询匹配最大值所在行
核心思路:先在子查询里算出每个分组的 MAX(score),再用外层查询把原表和这个结果按分组字段(如 category)和最大值双重匹配。注意必须同时匹配分组键和最大值,否则可能跨组误匹配。
常见错误现象:
– 只按分组字段连接,没校验 score = max_score,导致返回多行或错误行
– 子查询没加 GROUP BY,结果集维度不对,连接失败
- 假设表叫
products,要查每个type下price最高的商品完整信息 - 子查询必须写成:
SELECT type, MAX(price) AS max_price FROM products GROUP BY type - 外层
JOIN或WHERE ... IN (subquery)都要同时满足t1.type = t2.type且t1.price = t2.max_price - 如果存在并列最高(多个同 price),此方法会返回全部——这是正确行为,不是 bug
SELECT p1.* FROM products p1 INNER JOIN ( SELECT type, MAX(price) AS max_price FROM products GROUP BY type ) p2 ON p1.type = p2.type AND p1.price = p2.max_price;
注意 NULL 和重复值带来的意外结果
MAX() 会自动忽略 NULL,但如果某分组所有 price 都是 NULL,子查询中该分组不会出现在结果里,外层连接后对应分组就完全丢失——查不到任何记录。另外,若业务上要求“只取一条”,而存在多个相同最大值,需额外加 ORDER BY id LIMIT 1 等逻辑,但标准 SQL 中不能直接在关联子查询里这么做,得换用窗口函数或 ROW_NUMBER()。
- 检查是否有全
NULL分组:SELECT type FROM products GROUP BY type HAVING COUNT(price) = 0 - 若必须单条且有主键,可在外层加
AND p1.id = (SELECT MIN(id) FROM products p3 WHERE p3.type = p1.type AND p3.price = p1.price) - MySQL 8.0+ / PostgreSQL 推荐改用
ROW_NUMBER() OVER (PARTITION BY type ORDER BY price DESC, id ASC),更可控
性能关键点:索引怎么建才不慢
关联子查询本质是两趟扫描,若没索引,性能会随数据量陡增。重点不是给 price 单独建索引,而是建联合索引覆盖分组和排序字段。
- 最优索引:
INDEX(type, price)—— 支持子查询中的GROUP BY type和MAX(price),也能加速外层连接 - 避免只建
INDEX(price):无法加速分组,还可能让优化器选错执行计划 - 如果常按其他字段过滤(如
status = 'active'),索引应扩展为INDEX(type, status, price),并确保WHERE条件顺序匹配
实际跑之前,务必用 EXPLAIN 看子查询是否用了索引,以及连接类型是不是 ref 或 eq_ref,而不是 ALL。











