group by 严格按 from→where→group by→having→select→order by 顺序执行,非“先分组再算”;where 过滤原始行,having 筛选分组后结果且不可用别名;select 非聚合列必须全出现在 group by 中;多列分组是组合键而非嵌套;null 在分组中视为相等;无索引分组列易引发性能问题。

GROUP BY 的执行逻辑不是“先分组再算”,而是严格按顺序触发多个不可跳过的阶段——理解这点,才能避开 90% 的语法错误和性能陷阱。
GROUP BY 不是独立操作,而是查询执行链中的一环
它不单独存在,必须嵌入标准 SQL 执行流程:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。每个阶段输出都影响下一阶段的输入范围和结构。
-
WHERE过滤的是原始行,GROUP BY只能对WHERE留下的结果分组,不能过滤掉聚合后才出现的条件(比如“平均工资 > 10000”) -
HAVING是唯一能在分组后筛选的子句,但它只能引用SELECT中出现的列或聚合表达式,且不能用别名(如HAVING avg_salary > 10000报错,得写HAVING AVG(salary) > 10000) -
SELECT列表里所有非聚合列,必须 1:1 出现在GROUP BY子句中;否则 MySQL 8.0+ 默认报错(sql_mode=only_full_group_by开启时),PostgreSQL 和 SQL Server 始终强制此规则
GROUP BY 多列时,分组键是组合值,不是逐列独立分组
写 GROUP BY region, product_type,实际构建的是 (region, product_type) 的联合唯一键,不是先按 region 分、再在每组内按 product_type 分——后者是嵌套逻辑,SQL 不支持原生嵌套分组。
- 如果某行
region为 NULL,product_type为 'A',另一行region为 'East',product_type为 NULL,它们不会被归入同一组(NULL ≠ NULL 在分组中?错:SQL 标准规定 NULL 值在GROUP BY中被视为相等,所以两个 NULL 会进同一组) - 想实现“按 region 汇总,再按 region+product_type 细分”,得用
GROUPING SETS或 CTE +UNION ALL,而不是靠多列GROUP BY自动完成 - MySQL 5.7 默认关闭
only_full_group_by,可能让你误以为SELECT region, name, COUNT(*) FROM sales GROUP BY region能跑通——其实name是随机取某一行的值,结果不可靠
聚合函数的输入来自“虚拟分组行”,不是原始表行
执行 GROUP BY department 后,数据库内部会构造一个中间结构:每组对应一行,该行的非分组列(如 salary)以列表形式存在,聚合函数就是对这个列表运算。
-
COUNT(*)数的是该组原始行数,COUNT(salary)数的是该组中salary非 NULL 的行数 -
AVG()和SUM()会自动忽略 NULL,但MAX()/MIN()仍可能返回 NULL(当整组salary全为 NULL 时) - 不要在
WHERE中写salary > AVG(salary)——AVG()尚未计算,此时还没到GROUP BY阶段;得用子查询或窗口函数
真正容易被忽略的,是 GROUP BY 对索引的依赖:它本质上是一次内存或磁盘上的哈希/排序操作。没索引的分组列,数据量稍大就会触发临时表和 filesort,查询从毫秒级变成秒级。别只盯着 SELECT 写得多不多,先看 GROUP BY 列有没有索引。











