mysqlgroup by如何分组统计_mysql分组查询解析

来源:这里教程网 时间:2026-02-28 20:45:30 作者:

GROUP BY 后必须出现在 SELECT 中的字段有哪些?

MySQL 8.0.13+ 默认启用

sql_mode=ONLY_FULL_GROUP_BY
,这意味着:SELECT 列表里所有非聚合字段(如
name
status
)都必须明确出现在 GROUP BY 子句中。否则会报错:
Expression #1 of SELECT list is not in GROUP BY clause

常见错误写法:

SELECT id, name, COUNT(*) FROM users GROUP BY status;

这里

id
name
既没被聚合,也没在 GROUP BY 中,MySQL 直接拒绝执行。

正确做法是补全 GROUP BY:
GROUP BY id, name, status
(但通常这会失去分组意义)
更合理的是只选分组键 + 聚合结果:
SELECT status, COUNT(*), AVG(age) FROM users GROUP BY status
若真需取某组内任意一条记录的
name
,用
ANY_VALUE(name)
显式声明(MySQL 5.7.5+),避免关掉 ONLY_FULL_GROUP_BY

分组后怎么取每组最新/最大/最小的一条记录?

GROUP BY 本身不提供“取组内某行”的能力,它只负责聚合计算。想拿每组最新一条(比如按

create_time
最大),不能靠
MAX(create_time)
直接反查整行数据——那只是时间值,不是整条记录。

典型误操作:

SELECT user_id, MAX(create_time), content FROM logs GROUP BY user_id;

这段 SQL 在非 ONLY_FULL_GROUP_BY 模式下可能“跑通”,但

content
值是随机的(来自该组某条不确定的记录),不可靠。

安全做法是用窗口函数(MySQL 8.0+):
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC)
,再外层筛选
rn = 1
兼容老版本可用关联子查询或 LEFT JOIN 自连接,但性能差,尤其数据量大时 别用
GROUP_CONCAT
拼接后截取,逻辑脆弱且无法处理含逗号的字段值

WHERE 和 HAVING 的执行顺序与使用边界

WHERE
过滤的是原始行,
HAVING
过滤的是分组后的聚合结果。二者不能互换,且执行顺序固定:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。

例如要查“订单数超过 5 的用户”:

✅ 正确:
SELECT user_id, COUNT(*) c FROM orders GROUP BY user_id HAVING c > 5
❌ 错误:
SELECT user_id, COUNT(*) c FROM orders WHERE c > 5 GROUP BY user_id
——
c
是聚合结果,WHERE 阶段还不存在
⚠️ 注意:WHERE 可以用索引加速,HAVING 无法利用索引,纯内存过滤,大数据量时尽量把能前置的条件写在 WHERE 里(如
WHERE status = 'paid'

GROUP BY 性能差?先看有没有索引覆盖

GROUP BY 不一定慢,慢往往是因为没走索引。MySQL 对

GROUP BY
的优化依赖于 B+ 树索引的有序性——如果 GROUP BY 字段上有索引,且查询中涉及的字段(SELECT 列、WHERE 条件)能被该索引覆盖,就能避免临时表和文件排序。

检查执行计划:
EXPLAIN SELECT status, COUNT(*) FROM users GROUP BY status
,重点看
type
是否为
index
range
Extra
是否含
Using temporary; Using filesort
复合索引建议按
GROUP BY 字段 + WHERE 字段 + SELECT 中的非聚合字段
排序创建,例如:
INDEX(status, created_at, name)
对大表慎用
GROUP BY
配合
ORDER BY
不同字段,极易触发临时表;如必须排序,优先让排序字段也进入索引

分组统计真正的坑不在语法,而在对“GROUP BY 作用域”的误解——它只定义聚合维度,不定义行选取逻辑。很多看似“能跑”的 SQL,其实返回结果不可控,上线后数据对不上才暴露问题。

相关推荐