聚合函数与 NULL 的陷阱
五个常用聚合函数都有一个共同点:忽略 NULL。这意味着 AVG(commission_pct) 算的是「有提成的人的平均提成」,而不是「所有员工的平均提成」。这个差异在报表里往往是致命的。
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;在你的专用沙箱里执行打开演练场
GROUP BY 与 HAVING
WHERE 在分组前过滤「行」,HAVING 在分组后过滤「组」。这个区别决定了你不能把聚合函数写进 WHERE。
- SELECT 列表里非聚合的列,必须出现在 GROUP BY 中
- GROUP BY 里可以出现 SELECT 里没有的列,但不推荐
- HAVING 中可以用聚合函数,WHERE 中不行
- 性能上能用 WHERE 先过滤就不要留到 HAVING,减少参与分组的数据量
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;在你的专用沙箱里执行打开演练场
高级分组: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。
在你的专用沙箱里执行打开演练场
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;在你的专用沙箱里执行打开演练场
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。
在你的专用沙箱里执行打开演练场
常见错误清单
- ORA-00979: not a GROUP BY expression → SELECT 里有非聚合列没进 GROUP BY
- 在 WHERE 里用聚合函数 → 必须改用 HAVING
- 以为 GROUP BY 会保留原始行 → 分组后每组的原始行都合并了
- 对分组列使用函数(如 TRUNC(hire_date))后仍想用索引 → 需要函数索引