Oracle HAVING 与 GROUP BY 配合使用(HAVING 不能使用非聚合列,因为执行到 HAVING 时,非聚合列已经被分组操作销毁了,只剩下分组列和聚合结果供 HAVING 使用) Oracle HAVING 子句完全指南一、HAVING 的基本概念1.1 定义HAVING是 SQL 中用于对分组后的结果进行过滤的子句它与GROUP BY配合使用。1.2 核心定位textWHERE → 过滤行分组前 GROUP BY → 分组 HAVING → 过滤组分组后二、WHERE vs HAVING 对比这是最核心的理解对比项WHEREHAVING过滤时机分组之前过滤行分组之后过滤组能否使用聚合函数❌ 不能✅ 可以如 SUM, AVG, COUNT能否使用非聚合列✅ 可以⚠️ 必须在 GROUP BY 中或聚合函数中执行顺序先执行后执行示例对比sql-- ❌ WHERE 中不能使用聚合函数 SELECT deptno, AVG(sal) FROM emp WHERE AVG(sal) 2000 -- 报错 GROUP BY deptno; -- ✅ HAVING 可以使用聚合函数 SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING AVG(sal) 2000; -- 正确三、执行顺序关键理解sqlSELECT deptno, AVG(sal) AS avg_sal FROM emp WHERE hiredate DATE 1980-01-01 -- 1. 过滤行 GROUP BY deptno -- 2. 分组 HAVING AVG(sal) 2000 -- 3. 过滤组 ORDER BY avg_sal DESC; -- 4. 排序逻辑执行顺序FROM→ 获取基础数据WHERE→ 过滤不满足条件的行GROUP BY→ 对数据分组聚合计算→ 计算 SUM/AVG/COUNT 等HAVING→ 过滤分组后的结果SELECT→ 选择输出列ORDER BY→ 排序四、典型使用场景场景1过滤平均值sql-- 查询平均薪资大于2500的部门 SELECT deptno, AVG(sal) AS avg_sal, COUNT(*) AS emp_count FROM emp GROUP BY deptno HAVING AVG(sal) 2500;场景2过滤计数sql-- 查询员工人数大于5人的部门 SELECT deptno, COUNT(*) AS emp_count FROM emp GROUP BY deptno HAVING COUNT(*) 5; -- 查询各部门中薪资大于3000的员工人数 SELECT deptno, COUNT(*) AS high_salary_count FROM emp WHERE sal 3000 -- 先过滤行 GROUP BY deptno HAVING COUNT(*) 2; -- 再过滤组场景3过滤总和sql-- 查询总薪资大于10000的部门 SELECT deptno, SUM(sal) AS total_sal FROM emp GROUP BY deptno HAVING SUM(sal) 10000;场景4组合条件sql-- 查询平均薪资2000且员工数3的部门 SELECT deptno, AVG(sal) AS avg_sal, COUNT(*) AS cnt FROM emp GROUP BY deptno HAVING AVG(sal) 2000 AND COUNT(*) 3;场景5使用非聚合列sql-- HAVING 中可以使用 GROUP BY 中的列 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING deptno IN (10, 20); -- deptno 在 GROUP BY 中五、HAVING vs WHERE 的协作正确用法sql-- 先 WHERE 过滤行再 HAVING 过滤组 SELECT deptno, AVG(sal) AS avg_sal, COUNT(*) AS cnt FROM emp WHERE hiredate DATE 1982-01-01 -- 只统计1982年后入职的 GROUP BY deptno HAVING AVG(sal) 2000; -- 过滤平均薪资2000的部门错误用法对比sql-- ❌ 错误WHERE 不能使用聚合函数 SELECT deptno, AVG(sal) FROM emp WHERE AVG(sal) 2000 -- 报错 GROUP BY deptno; -- ✅ 正确HAVING 使用聚合函数 SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING AVG(sal) 2000;六、与开窗函数的对比HAVING分组过滤sql-- 找出平均薪资大于3000的部门 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING AVG(sal) 3000; -- 结果只返回符合条件的部门行数减少开窗函数保留所有行sql-- 显示所有员工及其部门平均薪资并只显示部门平均3000的记录 SELECT ename, deptno, sal, AVG(sal) OVER(PARTITION BY deptno) AS dept_avg FROM emp WHERE deptno IN ( -- 需要子查询配合 SELECT deptno FROM emp GROUP BY deptno HAVING AVG(sal) 3000 ); -- 结果保留所有员工行但只显示符合条件的部门七、高级用法7.1 多层分组sql-- 按部门和职位分组过滤平均薪资3000的组 SELECT deptno, job, AVG(sal) AS avg_sal, COUNT(*) AS cnt FROM emp GROUP BY deptno, job HAVING AVG(sal) 3000 ORDER BY deptno, job;7.2 复杂条件sql-- 查询平均薪资2000 且 最高薪资5000 的部门 SELECT deptno, AVG(sal) AS avg_sal, MAX(sal) AS max_sal, MIN(sal) AS min_sal FROM emp GROUP BY deptno HAVING AVG(sal) 2000 AND MAX(sal) 5000;7.3 使用别名Oracle限制sql-- ❌ Oracle 不允许在 HAVING 中使用列别名 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING avg_sal 2000; -- 报错avg_sal 无效标识符 -- ✅ 必须使用聚合函数本身 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING AVG(sal) 2000;八、常见错误及解决方案错误1SELECT 列不在 GROUP BY 中sql-- ❌ 错误 SELECT deptno, ename, AVG(sal) -- ename 不在 GROUP BY 中 FROM emp GROUP BY deptno; -- ✅ 正确要么加入 GROUP BY SELECT deptno, ename, AVG(sal) FROM emp GROUP BY deptno, ename; -- ✅ 正确要么用聚合函数 SELECT deptno, COUNT(ename) AS emp_count, AVG(sal) FROM emp GROUP BY deptno;错误2HAVING 与 WHERE 位置颠倒sql-- ❌ 错误HAVING 必须在 GROUP BY 之后 SELECT deptno, AVG(sal) FROM emp HAVING AVG(sal) 2000 GROUP BY deptno; -- 报错 -- ✅ 正确顺序 SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING AVG(sal) 2000;错误3WHERE 使用聚合函数sql-- ❌ 错误 SELECT deptno, AVG(sal) FROM emp WHERE AVG(sal) 2000 -- 报错 GROUP BY deptno; -- ✅ 正确 SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING AVG(sal) 2000;九、实战案例案例1找出销量最高的产品类别sql-- 查询总销售额大于10000的产品类别且该类别的产品数量3 SELECT category, SUM(sales) AS total_sales, COUNT(*) AS product_count, AVG(price) AS avg_price FROM products WHERE status active -- 只统计活跃产品 GROUP BY category HAVING SUM(sales) 10000 AND COUNT(*) 3 ORDER BY total_sales DESC;案例2月度销售分析sql-- 找出月销售额超过平均月销售额的月份 SELECT TO_CHAR(order_date, YYYY-MM) AS month, SUM(amount) AS month_sales, COUNT(*) AS order_count FROM orders WHERE order_date DATE 2023-01-01 GROUP BY TO_CHAR(order_date, YYYY-MM) HAVING SUM(amount) ( SELECT AVG(month_sales) FROM ( SELECT SUM(amount) AS month_sales FROM orders GROUP BY TO_CHAR(order_date, YYYY-MM) ) ) ORDER BY month;案例3部门薪资结构分析sql-- 查询平均薪资最高的3个部门 SELECT deptno, AVG(sal) AS avg_sal, MAX(sal) - MIN(sal) AS salary_range, COUNT(*) AS emp_count FROM emp WHERE sal 1000 -- 过滤低薪员工 GROUP BY deptno HAVING AVG(sal) 2000 -- 部门平均薪资2000 ORDER BY avg_sal DESC FETCH FIRST 3 ROWS ONLY;十、HAVING 核心要点速记口诀textWHERE 行过滤HAVING 组过滤 WHERE 在前HAVING 在后 WHERE 用普通列HAVING 用聚合列 执行顺序WHERE → GROUP BY → HAVING检查清单HAVING 是否在 GROUP BY 之后HAVING 中的非聚合列是否在 GROUP BY 中能用 WHERE 过滤的条件是否放到了 WHERE 中提高性能聚合函数的别名是否在 HAVING 中直接使用Oracle不支持总结HAVING 的本质分组后的过滤器特性说明作用对象分组GROUP BY后的结果关键能力可以使用聚合函数SUM, AVG, COUNT等执行时机WHERE之后SELECT之前性能优化能用WHERE过滤的条件不要放HAVING最佳实践能用 WHERE 过滤的放在 WHERE 中提高性能HAVING 专注于聚合后的过滤条件注意执行顺序避免语法错误Oracle 中 HAVING 不支持列别名掌握 HAVING您就能在分组分析中更加游刃有余having 不能用于开窗函数吧因为没分组效果等于where完全正确您的理解非常到位。我直接给出明确的结论核心结论HAVING 不能用于开窗函数因为开窗函数不减少行数不分组HAVING 是用来过滤分组GROUP BY后的结果集的。如果强行在开窗函数查询中使用 HAVING且没有 GROUP BY效果等同于 WHERE但这种写法毫无意义且容易造成误解。详细解析1. HAVING 的本质是过滤分组sql-- HAVING 依赖 GROUP BY 存在 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno -- 必须有 GROUP BY HAVING AVG(sal) 2000; -- 过滤分组后的结果2. 开窗函数不分组不减少行数sql-- 开窗函数保留所有行14行全部返回 SELECT ename, sal, SUM(sal) OVER(PARTITION BY deptno) AS dept_total FROM emp; -- 返回 14 行没有分组的概念3. 如果强行使用 HAVING无 GROUP BYsql-- 示例1HAVING 等同于 WHERE SELECT ename, sal, SUM(sal) OVER() AS total FROM emp HAVING sal 2000; -- 等价于 -- WHERE sal 2000 -- 因为只有一个组全表HAVING 过滤的是行 -- 示例2HAVING 使用聚合条件无 GROUP BY SELECT ename, sal, SUM(sal) OVER() AS total FROM emp HAVING SUM(sal) 10000; -- 因为全表只有一个组SUM(sal) 是固定的 -- 要么所有行都返回要么都不返回 -- 效果等同于WHERE (SELECT SUM(sal) FROM emp) 10000对比实验场景查询薪资大于平均薪资的员工sql-- ❌ 错误写法HAVING 开窗函数 SELECT ename, sal, AVG(sal) OVER() AS avg_sal FROM emp HAVING sal AVG(sal) OVER(); -- 报错或结果错误 -- ✅ 正确写法1使用 WHERE 子查询 SELECT ename, sal FROM emp WHERE sal (SELECT AVG(sal) FROM emp); -- ✅ 正确写法2使用开窗函数 子查询 WITH t AS ( SELECT ename, sal, AVG(sal) OVER() AS avg_sal FROM emp ) SELECT ename, sal, avg_sal FROM t WHERE sal avg_sal; -- 用 WHERE 过滤 -- ✅ 正确写法3使用 HAVING但需要先分组 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING AVG(sal) (SELECT AVG(sal) FROM emp);为什么说效果等于 WHERE情况1HAVING 中使用非聚合列sql-- 以下两句效果完全相同没有 GROUP BY 时 SELECT ename, sal, SUM(sal) OVER() AS total FROM emp HAVING sal 2000; SELECT ename, sal, SUM(sal) OVER() AS total FROM emp WHERE sal 2000;情况2HAVING 中使用聚合函数sql-- 以下两句效果相同没有 GROUP BY 时 SELECT ename, sal, SUM(sal) OVER() AS total FROM emp HAVING SUM(sal) 10000; -- 如果全表总薪资 10000返回所有行否则返回空 -- 等价于 SELECT ename, sal, SUM(sal) OVER() AS total FROM emp WHERE (SELECT SUM(sal) FROM emp) 10000;正确使用场景总结场景使用方式是否可行开窗函数 WHERE过滤行✅ 推荐开窗函数 HAVING无 GROUP BY相当于 WHERE⚠️ 可行但无意义开窗函数 HAVING有 GROUP BY分组后过滤✅ 可行但 HAVING 与开窗无关GROUP BY HAVING过滤分组✅ 标准用法正确示例开窗函数与 HAVING 共存sql-- 场景先分组汇总再用开窗函数计算占比 SELECT deptno, total_sal, RANK() OVER(ORDER BY total_sal DESC) AS rank_by_sal FROM ( SELECT deptno, SUM(sal) AS total_sal FROM emp GROUP BY deptno HAVING SUM(sal) 5000 -- 先过滤分组 ) ORDER BY rank_by_sal;这里 HAVING 是作用于内层查询的 GROUP BY而不是作用于外层的开窗函数。最终结论您的理解 100% 正确HAVING 不能直接用于开窗函数因为开窗函数不执行分组聚合在没有GROUP BY的情况下使用HAVING效果等同于WHERE正确做法过滤行 → 使用WHERE过滤分组 → 使用GROUP BY ... HAVING过滤开窗结果 → 使用子查询 WHERE记忆口诀textWHERE 过滤行HAVING 过滤组 开窗不分组HAVING 不靠谱 若要过滤开窗子查询 WHERE 来帮忙HAVING 为什么不能使用非聚合列这个问题问得非常深入HAVING 为什么不能使用非聚合列涉及到 SQL 的执行逻辑和关系代数的基础。我们直接切入核心这不是 Oracle 的限制而是数学逻辑上的不可能。一、直观理解HAVING 处理的是组不是行1.1 核心逻辑矛盾WHERE处理行一条一条的记录HAVING处理组一群记录的集合当 SQL 执行到 HAVING 时原始的行已经不存在了只剩下组的汇总信息。1.2 举例说明sql-- 假设有 14 行员工数据 SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno;执行过程原始数据行text7369, SMITH, 20, 800 7499, ALLEN, 30, 1600 7521, WARD, 30, 1250 ...GROUP BY deptno分组text部门 10{行1, 行2, ...} 部门 20{行3, 行4, ...} 部门 30{行5, 行6, ...}聚合计算text部门 10AVG(sal) 2916.67 部门 20AVG(sal) 2175 部门 30AVG(sal) 1566.67HAVING 过滤sqlHAVING AVG(sal) 2000此时原始的行如 SMITH、ALLEN已经丢失HAVING 只能看到text部门 10, 2916.67 ← 这个组还存在 部门 20, 2175 ← 这个组还存在 部门 30, 1566.67 ← 这个组不存在了问题来了如果允许HAVING ename SMITH但ename在这个阶段已经不存在了怎么办二、SQL 执行顺序的残酷真相2.1 逻辑执行顺序sqlFROM -- 1. 获取表 WHERE -- 2. 过滤行ename 还存在 GROUP BY -- 3. 分组ename 被舍弃只保留分组列 聚合函数 -- 4. 计算如 AVG, SUM, COUNT HAVING -- 5. 过滤组这时只能看到分组列和聚合结果 SELECT -- 6. 输出 ORDER BY -- 7. 排序2.2 关键点当执行到HAVING时数据库已经丢弃了非分组列的具体值。sql-- 假设这个 SQL SELECT deptno, AVG(sal) AS avg_sal FROM emp GROUP BY deptno HAVING deptno 10; -- 注意deptno 是分组列还保留着deptno是分组列保留ename不是分组列已被丢弃所以HAVING ename SMITH相当于在问一个已经不存在的列数据库无法回答。三、特殊情况非聚合列在 GROUP BY 中3.1 规则如果非聚合列在GROUP BY中它就变成了分组列可以在 HAVING 中使用。sql-- 按部门和职位分组 SELECT deptno, job, AVG(sal) AS avg_sal FROM emp GROUP BY deptno, job -- job 现在是分组列 HAVING job MANAGER; -- 可以使用3.2 为什么可以因为job是分组列在分组过程中被保留了。执行过程分组键(deptno, job)分组结果text(10, PRESIDENT) → {行1, 行2, ...} (10, MANAGER) → {行3, 行4, ...} (20, MANAGER) → {行5, 行6, ...}job作为分组键的一部分始终存在所以 HAVING 可以引用四、图解对比4.1 WHERE处理行text┌─────────────────────────────────────┐ │ 原始表14行 │ │ ┌─────┬───────┬────────┬──────┐ │ │ │7369 │ SMITH │ 20 │ 800 │ │ │ │7499 │ ALLEN │ 30 │ 1600 │ │ │ │7521 │ WARD │ 30 │ 1250 │ │ │ │... │ ... │ ... │ ... │ │ │ └─────┴───────┴────────┴──────┘ │ └─────────────────────────────────────┘ ↓ WHERE ename SMITH ┌─────────────────────────────────────┐ │ 过滤后的行1行 │ │ ┌─────┬───────┬────────┬──────┐ │ │ │7369 │ SMITH │ 20 │ 800 │ │ │ └─────┴───────┴────────┴──────┘ │ └─────────────────────────────────────┘4.2 HAVING处理组text┌─────────────────────────────────────┐ │ 原始表14行 │ │ ┌─────┬───────┬────────┬──────┐ │ │ │7369 │ SMITH │ 20 │ 800 │ │ │ │7499 │ ALLEN │ 30 │ 1600 │ │ │ │... │ ... │ ... │ ... │ │ │ └─────┴───────┴────────┴──────┘ │ └─────────────────────────────────────┘ ↓ GROUP BY deptno ┌─────────────────────────────────────┐ │ 分组结果组 │ │ ┌────────┬──────────┬──────────┐ │ │ │ deptno │ AVG(sal) │ COUNT(*) │ │ │ │ 10 │ 2916.67 │ 3 │ │ │ │ 20 │ 2175 │ 5 │ │ │ │ 30 │ 1566.67 │ 6 │ │ │ └────────┴──────────┴──────────┘ │ │ ↑ 这时只有 deptno 和聚合结果 │ │ 原始行包括 ename已不存在 │ └─────────────────────────────────────┘ ↓ HAVING AVG(sal) 2000 ┌─────────────────────────────────────┐ │ 过滤后的组 │ │ ┌────────┬──────────┬──────────┐ │ │ │ deptno │ AVG(sal) │ COUNT(*) │ │ │ │ 10 │ 2916.67 │ 3 │ │ │ │ 20 │ 2175 │ 5 │ │ │ └────────┴──────────┴──────────┘ │ └─────────────────────────────────────┘五、为什么不能像 WHERE 那样灵活5.1 WHERE 的工作方式sqlWHERE ename SMITH逐行检查ename字段每行独立判断5.2 HAVING 的工作方式sqlHAVING AVG(sal) 2000检查组部门的聚合结果一个组一个判断5.3如果允许 HAVING ename SMITH一个组里有多个员工如部门 20 有 5 个员工如果其中一个是 SMITH整个部门是否满足条件如果部门里没有 SMITH但平均薪资 2000算不算这会导致逻辑混乱HAVING 的设计初衷就是组的整体属性而不是组内某个成员的属性。六、正确做法如果要在分组后引用非聚合列方法1将列加入 GROUP BYsqlSELECT deptno, job, AVG(sal) AS avg_sal FROM emp GROUP BY deptno, job -- job 加入分组 HAVING job MANAGER; -- 现在可以用了方法2使用子查询sql-- 先找出符合条件的组再关联回原表 SELECT * FROM emp WHERE deptno IN ( SELECT deptno FROM emp GROUP BY deptno HAVING AVG(sal) 2000 ) AND ename SMITH; -- 在外部 WHERE 过滤方法3使用开窗函数推荐sql-- 不分组保留所有行 SELECT ename, deptno, sal, AVG(sal) OVER(PARTITION BY deptno) AS dept_avg FROM emp WHERE dept_avg 2000 -- 使用子查询或 CTE AND ename SMITH;七、总结HAVING 不能使用非聚合列的根本原因原因说明逻辑层面HAVING 处理的是组不是行非聚合列在分组后已丢失执行顺序HAVING 在 GROUP BY 之后执行原始行已被舍弃设计意图HAVING 用于过滤组的整体属性如平均值、总和、计数数学基础关系代数中分组操作后只能保留分组键和聚合结果记忆要点textWHERE 是行过滤器可以看每个人的信息 HAVING 是组过滤器只能看组的整体信息 要过滤某个人 → WHERE 要过滤某个部门 → GROUP BY HAVING 两者都要 → 先 HAVING 再子查询 WHERE一句话总结HAVING 不能使用非聚合列是因为在执行到 HAVING 时非聚合列已经被分组操作销毁了只剩下分组列和聚合结果供 HAVING 使用。