Oracle GROUP BY 基础教程
一、GROUP BY 是什么?
GROUP BY 是 SQL 中用于将结果集按一个或多个列进行分组 的子句,通常与聚合函数 (如 COUNT、SUM、AVG等)配合使用,用于对分组后的数据进行统计分析。
核心作用:将相同分组条件的行"合并"为一组,并对每组计算汇总结果。
二、基本语法结构
SELECT 分组列, 聚合函数(列)
FROM 表名
[WHERE 筛选条件]
GROUP BY 分组列
[HAVING 分组筛选条件]
[ORDER BY 排序列];
执行顺序 :FROM→ WHERE→ GROUP BY→ HAVING→ SELECT→ ORDER BY
三、核心概念与示例
以员工表 EMP为例(字段:empno, ename, job, sal, deptno):
-- 表结构参考
CREATE TABLE emp (
empno NUMBER(4),
ename VARCHAR2(10),
job VARCHAR2(9),
sal NUMBER(7,2),
deptno NUMBER(2)
);
1. 单字段分组
需求:统计每个部门的员工人数。
SELECT deptno, COUNT(*) AS 员工人数
FROM emp
GROUP BY deptno;
结果:
| DEPTNO | 员工人数 |
|---|---|
| 10 | 3 |
| 20 | 5 |
| 30 | 6 |
2. 多字段分组
需求:统计每个部门、每种岗位的员工人数。
SELECT deptno, job, COUNT(*) AS 员工人数
FROM emp
GROUP BY deptno, job
ORDER BY deptno, job;
关键点 :GROUP BY deptno, job表示先按 deptno分组,再按 job分组(嵌套分组)。
3. 常用聚合函数
| 函数 | 作用 | 示例 |
|---|---|---|
COUNT(*) |
统计行数(含NULL) | COUNT(*) |
COUNT(列) |
统计非NULL值行数 | COUNT(comm) |
SUM(列) |
求和 | SUM(sal) |
AVG(列) |
求平均值 | AVG(sal) |
MAX(列) |
最大值 | MAX(sal) |
MIN(列) |
最小值 | MIN(hiredate) |
示例:统计各部门工资总和、平均工资、最高/最低工资
SELECT deptno,
SUM(sal) AS 总工资,
ROUND(AVG(sal), 2) AS 平均工资,
MAX(sal) AS 最高工资,
MIN(sal) AS 最低工资
FROM emp
GROUP BY deptno;
4. WHERE 与 HAVING 的区别
| 子句 | 作用 | 执行时机 | 能否用聚合函数 |
|---|---|---|---|
WHERE |
筛选行(分组前过滤) | GROUP BY前 |
❌ 不能 |
HAVING |
筛选组(分组后过滤) | GROUP BY后 |
✅ 能 |
示例1:统计工资大于2000的员工所在的部门人数(WHERE)
SELECT deptno, COUNT(*) AS 人数
FROM emp
WHERE sal > 2000 -- 先过滤工资>2000的行
GROUP BY deptno;
示例2:统计各部门人数,只显示人数大于3的部门(HAVING)
SELECT deptno, COUNT(*) AS 人数
FROM emp
GROUP BY deptno
HAVING COUNT(*) > 3; -- 再过滤分组后的结果
组合使用:
-- 统计各部门中工资>2000的员工人数,只显示人数>2的部门
SELECT deptno, COUNT(*) AS 人数
FROM emp
WHERE sal > 2000
GROUP BY deptno
HAVING COUNT(*) > 2;
四、重要规则与陷阱
1. SELECT 后的列必须满足"分组列或聚合函数"
错误写法(Oracle会报错):
SELECT deptno, ename, COUNT(*) -- ename 既不在GROUP BY中,也不是聚合函数
FROM emp
GROUP BY deptno;
正确写法:
-- 写法1:ename加入分组
SELECT deptno, ename, COUNT(*)
FROM emp
GROUP BY deptno, ename;
-- 写法2:对ename使用聚合函数(如取最大姓名)
SELECT deptno, MAX(ename) AS 员工姓名, COUNT(*)
FROM emp
GROUP BY deptno;
2. 分组列中的 NULL 值
-
GROUP BY会将所有NULL值归为同一组。-- 假设comm列有NULL值,统计各奖金值的员工数
SELECT comm, COUNT(*)
FROM emp
GROUP BY comm;
-- 结果中会有一行comm为NULL,表示该组是奖金为空的员工
3. ORDER BY 排序
-
默认按
GROUP BY列升序排列,可显式指定:SELECT deptno, COUNT(*) AS cnt
FROM emp
GROUP BY deptno
ORDER BY cnt DESC; -- 按人数降序排列
五、高级用法
1. 使用别名(Oracle特性)
Oracle 允许在 GROUP BY中使用列别名(部分数据库不支持):
SELECT deptno AS 部门编号, COUNT(*) AS cnt
FROM emp
GROUP BY 部门编号 -- 使用别名
ORDER BY cnt;
2. 与 DISTINCT 的区别
-
DISTINCT仅去重,GROUP BY可配合聚合函数统计。-- 查询有哪些部门(去重)
SELECT DISTINCT deptno FROM emp;-- 统计各部门人数(分组统计)
SELECT deptno, COUNT(*) FROM emp GROUP BY deptno;
3. ROLLUP/CUBE(多维统计)
-
ROLLUP:生成分组的小计和总计(层次化统计)。 -
CUBE:生成所有可能的分组组合(交叉统计)。
示例(ROLLUP):统计各部门工资总和,以及所有部门的总工资
SELECT deptno, SUM(sal) AS 总工资
FROM emp
GROUP BY ROLLUP(deptno);
结果:
| DEPTNO | 总工资 |
|---|---|
| 10 | 8750 |
| 20 | 10875 |
| 30 | 9400 |
| NULL | 29025 |
4. GROUPING SETS(自定义分组)
-- 同时统计部门分组和岗位分组
SELECT deptno, job, SUM(sal)
FROM emp
GROUP BY GROUPING SETS ((deptno), (job));
六、性能优化建议
-
先用 WHERE 过滤:减少分组的数据量(WHERE 在 GROUP BY 前执行)。
-
避免 SELECT*:只选择必要的列,减少 I/O。
-
合理使用索引 :对
GROUP BY列建立索引可提升分组效率(尤其数据量大时)。 -
慎用 HAVING:HAVING 是对分组后的结果过滤,尽量用 WHERE 提前过滤。
七、综合案例
需求:查询各部门中工资大于2000的员工人数,只显示人数≥2的部门,并按人数降序排列。
SELECT deptno,
COUNT(*) AS 人数,
AVG(sal) AS 平均工资
FROM emp
WHERE sal > 2000 -- 先过滤工资>2000的行
GROUP BY deptno
HAVING COUNT(*) >= 2 -- 再过滤分组后的人数≥2的组
ORDER BY 人数 DESC; -- 最后按人数降序排列
八、总结
| 功能 | 语法示例 |
|---|---|
| 单字段分组 | GROUP BY deptno |
| 多字段分组 | GROUP BY deptno, job |
| 分组前过滤 | WHERE sal > 2000 |
| 分组后过滤 | HAVING COUNT(*) > 3 |
| 聚合统计 | COUNT(*), SUM(sal), AVG(sal) |
| 排序 | ORDER BY 聚合列 DESC |