Oracle GROUP BY 基础教程

Oracle GROUP BY 基础教程

一、GROUP BY 是什么?

GROUP BY ​ 是 SQL 中用于将结果集按一个或多个列进行分组 的子句,通常与聚合函数 (如 COUNTSUMAVG等)配合使用,用于对分组后的数据进行统计分析。

核心作用:将相同分组条件的行"合并"为一组,并对每组计算汇总结果。


二、基本语法结构

复制代码
SELECT 分组列, 聚合函数(列)
FROM 表名
[WHERE 筛选条件]
GROUP BY 分组列
[HAVING 分组筛选条件]
[ORDER BY 排序列];

执行顺序FROMWHEREGROUP BYHAVINGSELECTORDER 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));

六、性能优化建议

  1. 先用 WHERE 过滤:减少分组的数据量(WHERE 在 GROUP BY 前执行)。

  2. 避免 SELECT*:只选择必要的列,减少 I/O。

  3. 合理使用索引 :对 GROUP BY列建立索引可提升分组效率(尤其数据量大时)。

  4. 慎用 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