GROUP BY 的作用是什么?使用时有哪些规则?
简化版
GROUP BY 按指定键把输入行划分为若干组,聚合函数再对每组计算一个结果。分组查询的 SELECT 列通常只能是分组列、聚合表达式,或能由分组键唯一确定的列;否则结果没有确定含义。
详细版
SELECT department_id,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM employee
GROUP BY department_id;
该查询每个部门输出一行。COUNT、SUM、AVG、MIN、MAX 等聚合函数通常忽略 NULL,但 COUNT(*) 统计行数,包括列值为 NULL 的行。
若 SELECT 同时写 department_id, employee_name, COUNT(*),而一个部门存在多个员工,数据库无法确定该显示哪个名字。MySQL 开启默认的 ONLY_FULL_GROUP_BY 时会拒绝这类不确定查询。
完整版教学
一、分组把多行压缩成一行
不带 GROUP BY 的聚合查询把所有输入行视为一个组:
SELECT COUNT(*), AVG(salary) FROM employee;
加入 GROUP BY department_id 后,每个不同的部门编号形成一组,每组分别计算聚合值。分组键可以有多个,此时按键的组合分组:
GROUP BY department_id, job_level
只有两个字段都相同的行才属于同一组。
二、SELECT 列为什么受到限制
分组后每组只输出一行。分组列在组内值相同,聚合函数能把多个值归约成一个值;普通非分组列却可能有多个候选值,所以不能随意输出。
SQL 标准允许输出函数依赖于分组键的列,例如按主键分组时,同一行的其他列由主键唯一确定。但不同数据库与版本对函数依赖的识别能力不同。为了清晰和可移植,面试与工程中应先解释“分组列或聚合列”这一基本规则,再说明函数依赖是更精确的例外。
三、NULL 如何参与分组和聚合
分组时,所有 NULL 分组键会归入同一组。聚合函数方面:
COUNT(*)统计组内行数;COUNT(column)只统计该列非NULL的行;SUM、AVG、MIN、MAX通常忽略NULL;- 若一组没有任何非
NULL输入,许多聚合函数返回NULL,而COUNT返回0。
需要把空值当作零参与业务计算时,应根据语义使用 COALESCE,不能默认数据库会把 NULL 当成 0。
四、WHERE 与 HAVING 的分工
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) >= 10000;
WHERE 先留下已支付订单,GROUP BY 再按客户分组,HAVING 最后筛选累计金额达标的客户。行级条件前置通常能减少分组工作量。
五、GROUP BY 不保证排序
分组结果看起来可能按分组键排列,但 SQL 不承诺没有 ORDER BY 的输出顺序。若业务要求稳定顺序,必须显式写:
ORDER BY total DESC, customer_id ASC
同理,依赖某次执行“碰巧返回第一行”的代码也不可靠。
六、常见误区与追问
| SELECT 中的表达式 | 是否通常允许 | 原因 |
|---|---|---|
| 分组列 | 是 | 每组内该值确定 |
| 聚合函数 | 是 | 多行被归约为一个值 |
| 非分组普通列 | 否 | 组内可能有多个候选值 |
| 由分组键唯一决定的列 | 视数据库而定 | 涉及函数依赖识别 |
- 误区:GROUP BY 后可以随便 SELECT 任意列。 分组后每组只输出一行,普通列如果既不是分组键也不能由分组键唯一确定,就没有确定语义。
- 误区:GROUP BY 会自动排序。 分组实现可能碰巧按某种顺序输出,但 SQL 结果顺序只有
ORDER BY才保证。 - 误区:聚合函数都会把 NULL 当 0。 多数聚合会忽略
NULL,SUM/AVG在全为空时可能返回NULL,只有COUNT的计数规则不同。 - 追问:为什么 MySQL 的 ONLY_FULL_GROUP_BY 很重要? 它会拒绝不确定的分组查询,避免从组内随机取一个非分组列导致线上结果随执行计划变化。
- 追问:WHERE 和 HAVING 能互换吗? 行级条件放
WHERE,聚合后条件放HAVING;能提前过滤的条件尽量前置,减少分组输入量。 - 追问:按主键 GROUP BY 时能不能 SELECT 其他列? 理论上主键能唯一决定同一行的其他列,但可移植写法仍建议显式理解函数依赖,不要把宽松数据库行为当标准。
记忆钩子:GROUP BY 的核心是“每组只剩一行”,所以输出列必须能在这一组里得到唯一答案。
七、加强记忆
GROUP BY 先按键把行装进不同的组,聚合函数再把每组压成一个结果。输出列必须能为每组确定唯一值;NULL 分组键归为一组,多数聚合忽略 NULL,COUNT(*) 仍统计整行。分组不负责排序,稳定顺序必须写 ORDER BY。