GROUP BY 位于 WHERE 之后、HAVING 之前、SELECT 计算之前,将过滤后的结果按指定列分组,每组生成一行供后续聚合函数使用。

GROUP BY 在 SQL 执行流程中的位置
MySQL 执行一条含 GROUP BY 的查询时,GROUP BY 不是“先过滤再分组”,也不是“最后才分组”,而是发生在 WHERE 之后、HAVING 之前、SELECT 列计算之前的关键阶段。它属于逻辑执行顺序中「分组聚合」这一环节,直接影响后续所有聚合函数(如 COUNT()、SUM())的输入数据集。
标准逻辑执行顺序与 GROUP BY 的实际作用点
虽然 MySQL 物理执行可能因优化器重排(比如下推条件),但语义上遵循如下逻辑顺序:
-
FROM→JOIN→ 确定原始数据源 -
WHERE→ 过滤行(此时还无分组概念) -
GROUP BY→ 将满足WHERE的结果按指定列/表达式划分为多个组,每组产生一行中间结果 -
HAVING→ 对每个组的聚合结果进行过滤(可引用GROUP BY列和聚合函数) -
SELECT→ 计算最终输出列(非聚合列必须出现在GROUP BY中,否则报错sql_mode=only_full_group_by) -
ORDER BY/LIMIT→ 最后排序或截断
注意:GROUP BY 后每一行代表一个组,原始表中多行被压缩为一行;后续所有聚合函数都在这个“每组一行”的上下文中运行。
常见误解与典型错误场景
很多问题源于混淆了“行级操作”和“组级操作”的边界:
- 在
WHERE子句里写聚合函数(如WHERE COUNT(*) > 1)会报错——因为WHERE执行时还没分组,COUNT(*)无意义 - 把本该放
HAVING的条件误写进WHERE(例如想查“订单数 > 5 的用户”,却写成WHERE COUNT(order_id) > 5) - 开启
ONLY_FULL_GROUP_BY模式后,SELECT中出现未在GROUP BY中声明的非聚合字段(如SELECT user_id, name FROM orders GROUP BY user_id),MySQL 会拒绝执行 - 对
GROUP BY字段使用函数但未在SELECT或HAVING中保持一致(如GROUP BY DATE(created_at),却在SELECT中写created_at)
简单验证流程的 SQL 示例
用以下语句可以直观看到各阶段效果:
SELECT user_id, COUNT(*) AS cnt FROM orders WHERE status = 'paid' GROUP BY user_id HAVING COUNT(*) >= 3 ORDER BY cnt DESC;
执行过程分解:
-
WHERE status = 'paid':先筛出已支付订单 -
GROUP BY user_id:将这些订单按用户归并,每个user_id成为一个组 -
COUNT(*)在每个组内统计行数,生成临时列cnt -
HAVING COUNT(*) >= 3:只保留订单数 ≥ 3 的用户组 -
SELECT输出user_id和cnt,ORDER BY再排序
真正容易被忽略的是:一旦进入 GROUP BY 阶段,原始行就不可见了。所有后续操作都只能基于“组”而非“行”。如果你需要组内明细,得用子查询、窗口函数或者 GROUP_CONCAT() 这类特殊聚合来绕过限制。










