做报表或者数据统计的时候,我们经常需要先按某个维度分组汇总,然后再从汇总结果里挑出符合条件的那部分数据。比如统计每个部门的平均工资后,只想看平均工资超过一万的部门;或者统计每个客户的订单数,只关注下单超过50次的客户。很多刚接触SQL的人会习惯性地把条件写进WHERE子句,结果发现查询要么报错,要么结果不对。原因其实很简单:SQL的执行顺序里,WHERE在GROUP BY之前执行,它面对的还是一行行的原始记录,根本没有聚合结果可以比较。这个时候,就该HAVING子句登场了。

一、先弄清楚WHERE和HAVING的执行顺序
一条完整的查询语句,数据库内部的逻辑处理顺序大致是:FROM先确定数据来源,接着WHERE逐行过滤原始记录,然后GROUP BY进行分组,再由HAVING对分组后的结果做筛选,之后SELECT计算输出列,最后才是ORDER BY排序。理解了这个顺序,很多疑问就迎刃而解了。
最常见的错误写法就是把聚合函数塞进WHERE里,比如WHERE COUNT(*) > 10,DB2会直接抛出错误,提示WHERE子句中不允许使用聚合函数。因为WHERE执行时分组还没发生,COUNT(*)根本没有意义。而HAVING专门设计出来就是干这个的,它只作用于分组之后的结果集。
还有一点需要说明:HAVING里其实也可以写普通字段的条件,比如HAVING DEPT_ID = 10这种写法在DB2里语法上是允许的(前提是该字段出现在GROUP BY里),但从规范角度看,普通条件应该尽量放在WHERE中。原因是WHERE过滤发生在分组之前,能减少参与分组运算的数据量,对性能更有利。把本该在WHERE里处理的条件挪到HAVING,等于让数据库先做了一次无意义的全量分组。
二、HAVING子句的基础用法和常见示例
HAVING最常见的搭配对象就是COUNT、SUM、AVG、MAX、MIN这些聚合函数。下面通过一个订单表的例子来说明。假设有一张ORDER_TABLE表,包含CUSTOMER_ID(客户编号)、ORDER_AMOUNT(订单金额)、ORDER_DATE(下单日期)等字段,我们想找出下单次数超过5次的客户:
SELECT CUSTOMER_ID, COUNT(*) AS ORDER_COUNT FROM ORDER_TABLE GROUP BY CUSTOMER_ID HAVING COUNT(*) > 5 ORDER BY ORDER_COUNT DESC;
这条语句的执行过程可以拆解成三步:第一步按客户编号把所有订单分组;第二步对每个分组统计订单数量;第三步用HAVING把订单数小于等于5的分组剔除掉。如果还想进一步限定只看消费总额超过一万的客户,可以在HAVING里追加条件:
SELECT CUSTOMER_ID,
COUNT(*) AS ORDER_COUNT,
SUM(ORDER_AMOUNT) AS TOTAL_AMOUNT
FROM ORDER_TABLE
GROUP BY CUSTOMER_ID
HAVING COUNT(*) > 5
AND SUM(ORDER_AMOUNT) > 10000;
多个条件之间用AND或OR连接,逻辑上和WHERE的用法完全一致。这里有个容易踩的坑需要注意:HAVING里引用聚合结果时,最好直接写聚合表达式,而不是用SELECT里定义的列别名。有些数据库支持在HAVING中使用别名,但在DB2的不同版本里行为可能不一致,老老实实写SUM(ORDER_AMOUNT)这种完整表达式是最稳妥的做法,可移植性也更好。
三、HAVING与子查询、窗口函数的配合技巧
当筛选条件需要和整体数据做对比时,HAVING配合子查询会非常灵活。比如要找出平均订单金额高于全公司平均水平的部门,可以先在子查询里算出全局平均值,再让HAVING去逐组比较:
SELECT DEPT_ID, AVG(SALARY) AS AVG_SALARY
FROM EMPLOYEE
GROUP BY DEPT_ID
HAVING AVG(SALARY) > (
SELECT AVG(SALARY) FROM EMPLOYEE
);
这种写法在DB2的LUW版本和z/OS版本上都能正常工作。如果只想对部分数据做这种比较,记得在子查询里也加上同样的限定条件,否则对比基准就变了。
另一个进阶场景是找组内的最大或最小记录。比如每个部门工资最高的员工信息,传统写法是先分组求最大值再回表关联,而DB2从9.7版本开始支持窗口函数,可以用ROW_NUMBER()配合OVER(PARTITION BY ...)一步到位。虽然这已经不算HAVING的用法,但在很多老系统维护中,HAVING加自关联的写法仍然大量存在,读懂它们是维护遗留代码的基本功:
SELECT E.DEPT_ID, E.EMP_NAME, E.SALARY
FROM EMPLOYEE E
INNER JOIN (
SELECT DEPT_ID, MAX(SALARY) AS MAX_SAL
FROM EMPLOYEE
GROUP BY DEPT_ID
) T ON E.DEPT_ID = T.DEPT_ID AND E.SALARY = T.MAX_SAL;
四、性能优化和使用规范建议
HAVING子句本身不会拖慢查询,真正影响性能的是它背后的分组运算。优化的第一原则还是前面提到的:普通过滤条件放WHERE,聚合过滤条件放HAVING,让分组运算处理的数据尽可能少。举个实际的例子,统计最近一年下单超过10次的客户,应该把时间条件写在WHERE里,而不是先对全历史数据分组再用HAVING筛日期。
-- 推荐写法:先过滤再分组 SELECT CUSTOMER_ID, COUNT(*) AS CNT FROM ORDER_TABLE WHERE ORDER_DATE >= CURRENT DATE - 1 YEAR GROUP BY CUSTOMER_ID HAVING COUNT(*) > 10;
其次要注意索引的利用。GROUP BY和HAVING涉及的分组字段如果能命中索引,DB2可以避免额外的排序操作,性能提升明显。可以通过EXPLAIN或者db2expln查看执行计划,确认分组字段是否走索引扫描。另外,HAVING里的聚合表达式尽量保持简单,复杂的嵌套函数计算会让优化器难以选择高效的访问路径。
最后整理几条实用规范:HAVING必须和GROUP BY搭配使用,单独出现没有意义;HAVING中的非聚合字段必须出现在GROUP BY列表中,否则DB2会报SQLSTATE 42803错误;写完查询后养成习惯检查一遍,凡是能下推到WHERE的条件都下推。掌握这些细节,面对各种分组统计需求时就能写出既正确又高效的SQL了。
DB2 HAVING子句分组统计SQL过滤修改时间:2026-09-04 05:08:33