首页/数据/数据库速查/GROUP BY 分组统计
SQL 语法52 条

GROUP BY 分组统计

按一列或多列把行分组并配合聚合函数计算;8.0 默认开启 ONLY_FULL_GROUP_BY,选择列表必须与分组列一致或用聚合函数。

数据库速查数据更新于 2026-10-13

GROUP BY 是什么

GROUP BY 按一列或多列把行归并成组,组内用聚合函数计算,是统计类查询的基础。MySQL 8.0 默认开启 ONLY_FULL_GROUP_BY:SELECT 列表里的非聚合列必须出现在分组列中,否则直接报错。分组通常需要临时表或排序来支撑,分组键上有合适索引、尤其是与 WHERE 条件口径对齐时,开销会明显下降。过滤放在分组前用 WHERE,放在分组后用 HAVING,两者代价差别很大。

基本信息

用途分组统计与汇总
语法GROUP BY 列1, 列2 [WITH ROLLUP] [HAVING 条件]
默认约束ONLY_FULL_GROUP_BY,5.7 与 8.0 均默认开启
过滤时机WHERE 在分组前,HAVING 在分组后
常用聚合COUNT / SUM / AVG / MIN / MAX / GROUP_CONCAT
索引影响分组键与过滤条件对齐时可用索引,否则临时表加排序

典型使用场景

  • 按状态统计订单量与金额,作为业务看板的基础数据
  • 按天或按月汇总趋势,用日期列或日期函数分组
  • 多维度交叉统计:日期与状态、渠道与地区组合
  • 找出重复数据:按业务键分组后筛出计数大于一的组
  • 统计每个用户的订单数并倒序,定位活跃用户与异常账号
  • 用 WITH ROLLUP 生成带小计与合计的报表,减少后续加工

基础分组

基础分组
SELECT status, COUNT(*) AS cnt, SUM(amount) AS total   # 分组列加聚合列
FROM orders
WHERE created_at >= '2026-10-01'   # 先过滤再分组,代价最低
GROUP BY status
HAVING total > 1000   # 分组后再过滤,可引用聚合别名
ORDER BY total DESC;   # 按统计值排序

多列分组与小计

多列分组与小计
SELECT DATE(created_at) AS d, status, COUNT(*) AS cnt
FROM orders
GROUP BY d, status WITH ROLLUP   # 追加小计与总计行
ORDER BY d DESC, status;   # 小计行的分组列为空,取值时要区分

SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id HAVING cnt > 1   # 查重:分组后过滤
ORDER BY cnt DESC LIMIT 20;

常见写法与代价

写法含义注意点
GROUP BY 列按列归并分组非聚合列必须出现在分组列中,否则报错
WHERE ... GROUP BY先过滤再分组过滤越早,参与分组的行越少
HAVING对分组结果过滤能用 WHERE 表达的不要挪到 HAVING
GROUP BY 表达式按计算结果分组无法用索引,通常伴随临时表
WITH ROLLUP追加小计与总计行小计行的分组列为空,读取时须区分
DISTINCT去重不做聚合时语义更清晰,优先选它
ORDER BY 聚合结果按统计值排序可引用别名,排序发生在分组之后
能用 WHERE 过滤的条件不要放进 HAVING:WHERE 在分组前剔除行,参与分组的行更少、临时表更小;HAVING 只能过滤算完的分组结果,等于把代价留到了最后。
分组列为 NULL 的行会被归成同一组,统计结果里会多出一个分组列为空的组;报表里要显式处理这类记录,否则容易被误当成脏数据。
关闭 ONLY_FULL_GROUP_BY 只是让有歧义的语句不再报错,非聚合列取到的是组内任意一行的值,结果不确定;正确做法是补齐分组列或用聚合函数包裹。

分组写法检查清单

  • 分组键尽量与过滤条件对齐,例如先按状态过滤再按状态分组
  • 索引建在分组键与过滤列上,聚合列本身不需要索引
  • 统计口径先对齐:不同计数写法对空值处理不同,混用会让数字对不上
  • 需要精确去重计数时用去重聚合,但大表开销高,可评估近似方案
  • 小计与总计交给分组小计语法或单独查询,别用程序逐行累加
  • 分组列上存在大量空值时先确认业务含义,再决定是否归并
统计所有行、统计某列非空的行、去重计数是三种不同口径:前者不忽略空值,中间那种会跳过该列为空的行,去重计数还要额外排序去重,同一报表里混用会让汇总数字对不上。

易错点

  • SELECT 里出现未分组的非聚合列:默认约束下直接报错,临时关闭约束又会得到不确定的值,应补齐分组列
  • 把过滤条件写在 HAVING 里:先分组再过滤,参与计算的行数多、临时表大,能下推的条件必须下推到 WHERE
  • 对分组列套函数(如 DATE(created_at)):索引失效并伴随临时表与排序,可考虑冗余一个日期列
  • 忘记 NULL 会自成一组:统计口径与预期不符、报表出现空分组行,需用函数归并或单独过滤
  • 用 WITH ROLLUP 后程序仍按分组列解析:小计行的分组列为空,不做区分就会把小计当明细重复累计

数据来源与参考

  • MySQL 8.0 Reference Manual: GROUP BY Optimization

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌