知识库✳

教程与手册

全部教程
  1. 01Oracle 函数手册
  2. 02性能优化手册
  3. 03Linux Shell
  4. 04Oracle 学习与演练
  5. 05网络基础学习
  6. 06数据治理与管理
返回个人网站
甲Oracle 学习教程从 SQL 到 PL/SQL 与运维
首页课程目录搜索演练场
知识库

已完成 0 / 22 章

模块一 · 入门与准备

  • 01Oracle 数据库体系结构
  • 02环境安装与连接方式
  • 03SQL*Plus 与常用工具

模块二 · SQL 查询基础

  • 04SELECT 查询基础
  • 05过滤与排序
  • 06单行函数
  • 07多表连接
  • 08分组聚合与 HAVING
  • 09子查询

模块三 · 数据与对象

  • 10DML 与事务控制
  • 11表与数据类型
  • 12约束
  • 13索引与执行计划
  • 14视图、序列与同义词

模块四 · PL/SQL 编程

  • 15PL/SQL 块与变量
  • 16流程控制与游标
  • 17存储过程与函数
  • 18触发器
  • 19异常处理

模块五 · 运维与进阶

  • 20用户与权限管理
  • 21表空间与存储管理
  • 22备份与恢复
❖

Oracle 学习教程

返回个人网站知识库

示例代码基于 Oracle AI Database 26ai / 23ai,查询示例使用官方 HR 示例方案。

以 Next.js 16 · Tailwind CSS v4 · HeroUI v3 装帧

08
  1. 首页
  2. 课程目录
  3. 分组聚合与 HAVING

第 8 章◆进阶◆建议 50 分钟◆模块二 · SQL 查询基础

分组聚合与 HAVING

用聚合函数做统计,用 GROUP BY 分组,用 HAVING 过滤分组结果,并掌握 ROLLUP 等高级分组。

学完这一章,你应该能

  • 熟练使用 COUNT/SUM/AVG/MIN/MAX
  • 分清 WHERE 与 HAVING 的适用场景
  • 会用 ROLLUP/CUBE 生成小计与合计
  • 理解聚合函数忽略 NULL 带来的偏差

8.1聚合函数与 NULL 的陷阱

五个常用聚合函数都有一个共同点:忽略 NULL。这意味着 AVG(commission_pct) 算的是「有提成的人的平均提成」,而不是「所有员工的平均提成」。这个差异在报表里往往是致命的。

COUNT 的三种写法差异很大
SELECT COUNT(*)                        AS all_rows,        -- 107,含所有行
       COUNT(commission_pct)           AS non_null_comm,   -- 只有 35
       COUNT(DISTINCT department_id)   AS distinct_dept,   -- 不同的部门数
       SUM(salary)                     AS total_salary,
       ROUND(AVG(salary), 2)           AS avg_salary,
       MIN(hire_date)                  AS first_hire,
       MAX(salary)                     AS max_salary
FROM   employees;
在你的专用沙箱里执行打开演练场
平均值到底在算谁
SELECT ROUND(AVG(commission_pct), 4)             AS avg_ignore_null,  -- 忽略 NULL
       ROUND(SUM(commission_pct)/COUNT(*), 4)      AS avg_include_null, -- 把 NULL 当 0 摊
       COUNT(*)                                    AS total_emp,
       COUNT(commission_pct)                       AS emp_with_comm
FROM   employees;
在你的专用沙箱里执行打开演练场

提示 写 AVG 前先问自己:分母该是全部记录还是有值的记录?想清楚再写,必要时用 SUM/COUNT 手动控制。

8.2GROUP BY 与 HAVING

WHERE 在分组前过滤「行」,HAVING 在分组后过滤「组」。这个区别决定了你不能把聚合函数写进 WHERE。

  • SELECT 列表里非聚合的列,必须出现在 GROUP BY 中
  • GROUP BY 里可以出现 SELECT 里没有的列,但不推荐
  • HAVING 中可以用聚合函数,WHERE 中不行
  • 性能上能用 WHERE 先过滤就不要留到 HAVING,减少参与分组的数据量
各部门平均工资(只要超过 3 人的部门)
SELECT d.department_name,
       COUNT(*)                 AS headcount,
       ROUND(AVG(e.salary), 2)  AS avg_salary,
       SUM(e.salary)            AS total_salary
FROM   employees   e
JOIN   departments d ON d.department_id = e.department_id
WHERE  e.salary > 3000           -- 分组前:过滤行
GROUP  BY d.department_name
HAVING COUNT(*) >= 3             -- 分组后:过滤组
ORDER  BY avg_salary DESC;
在你的专用沙箱里执行打开演练场

8.3高级分组:ROLLUP / CUBE / GROUPING SETS

生成分部门的小计与总计
SELECT department_id,
       job_id,
       COUNT(*)        AS cnt,
       SUM(salary)     AS total
FROM   employees
GROUP  BY ROLLUP(department_id, job_id)
ORDER  BY department_id NULLS LAST, job_id NULLS LAST;

ROLLUP 会逐级汇总:明细 → 部门小计 → 总计,小计行的分组列显示为 NULL。

在你的专用沙箱里执行打开演练场
用 GROUPING 区分「小计的 NULL」和「数据本身的 NULL」
SELECT department_id,
       GROUPING(department_id)  AS is_subtotal,   -- 1 表示这是汇总行
       SUM(salary)              AS total
FROM   employees
GROUP  BY ROLLUP(department_id)
ORDER  BY is_subtotal, department_id NULLS LAST;
在你的专用沙箱里执行打开演练场
LISTAGG:把多行拼成一行
SELECT department_id,
       LISTAGG(last_name, ', ') WITHIN GROUP (ORDER BY last_name) AS members
FROM   employees
WHERE  department_id IN (50, 60, 80)
GROUP  BY department_id
ORDER  BY department_id;

拼接结果默认上限 4000 字节,超长会报 ORA-01489,需要配合 ON OVERFLOW TRUNCATE。

在你的专用沙箱里执行打开演练场

8.4常见错误清单

  • ORA-00979: not a GROUP BY expression → SELECT 里有非聚合列没进 GROUP BY
  • 在 WHERE 里用聚合函数 → 必须改用 HAVING
  • 以为 GROUP BY 会保留原始行 → 分组后每组的原始行都合并了
  • 对分组列使用函数(如 TRUNC(hire_date))后仍想用索引 → 需要函数索引

练习

3 题 · 先自己写,再展开对照

参考答案

AVG 忽略 NULL,分母只算 commission_pct 非空的行(有提成的人),
所以算出来是「有提成的人的平均提成」,数值偏高。

SUM(commission_pct)/COUNT(*) 把没有提成的人按 0 摊进分母,
才是「所有员工的平均提成」。

写 AVG 之前先问:分母应该是全部记录,还是有值的记录。

自评要点

  • AVG 忽略 NULL
  • 能说清两种口径各自代表什么业务含义

参考答案

SELECT d.department_name,
       COUNT(*)                 AS headcount,
       ROUND(AVG(e.salary), 2)  AS avg_salary
FROM   employees   e
JOIN   departments d ON d.department_id = e.department_id
GROUP  BY d.department_name
HAVING COUNT(*) > 5
ORDER  BY headcount DESC;
在你的专用沙箱里执行打开演练场

自评要点

  • 聚合条件用 HAVING 而不是 WHERE
  • SELECT 中的非聚合列出现在 GROUP BY

参考答案

SELECT department_id,
       GROUPING(department_id) AS is_subtotal,   -- 1 表示这是汇总行
       COUNT(*)                AS cnt,
       SUM(salary)             AS total
FROM   employees
GROUP  BY ROLLUP(department_id)
ORDER  BY is_subtotal, department_id NULLS LAST;
在你的专用沙箱里执行打开演练场

自评要点

  • ROLLUP 逐级汇总:明细 → 小计 → 总计
  • 用 GROUPING() 区分汇总产生的 NULL 与数据本身的 NULL
← 上一章

多表连接

下一章 →

子查询