不含聚合函数的HAVING条件是否始终与WHERE完全等价?
我在PostgreSQL测试套件里看到一段有意思的注释,对应的SQL代码如下:
-- HAVING is effectively equivalent to WHERE in this case SELECT b, c FROM test_having GROUP BY b, c HAVING b = 3 ORDER BY b, c;
这让我产生疑问:当HAVING子句引用GROUP BY键列(非聚合值)时,是否在功能上完全等价于WHERE?有没有例外情况?
多数场景下两者等价
当HAVING仅过滤GROUP BY的键列时,通常和WHERE的效果一致。因为GROUP BY键列在每个分组中是唯一的——每个分组对应一组键列值,HAVING过滤这些分组,和先用WHERE过滤原始行(只保留符合键列条件的行)再分组,最终得到的分组集合完全相同。
存在等价性不成立的场景
1. 使用ROLLUP/CUBE/GROUPING SETS等扩展分组语法
这类语法会生成汇总分组行(比如ROLLUP会生成包含NULL的汇总行,代表更高层级的聚合)。此时HAVING过滤的是所有分组(包括汇总行),而WHERE是先过滤原始行再分组,两者结果会明显不同。
举个例子:
假设test_having表数据如下:
| b | c |
|---|---|
| 3 | 1 |
| 3 | 2 |
| 4 | 5 |
用WHERE的情况:
SELECT b, SUM(c) FROM test_having WHERE b = 3 GROUP BY ROLLUP(b);
结果会包含b=3的分组和对应的汇总行:
| b | sum |
|---|---|
| 3 | 3 |
| NULL | 3 |
用HAVING的情况:
SELECT b, SUM(c) FROM test_having GROUP BY ROLLUP(b) HAVING b = 3;
结果只会保留b=3的分组,过滤掉了b为NULL的汇总行:
| b | sum |
|---|---|
| 3 | 3 |
2. 结合GROUPING()函数判断分组类型
当使用GROUPING()函数区分普通分组和汇总分组时,HAVING可以基于这个函数过滤,而WHERE无法做到(因为GROUPING()是分组后计算的)。比如:
SELECT b, SUM(c), GROUPING(b) AS is_total FROM test_having GROUP BY ROLLUP(b) HAVING GROUPING(b) = 0 AND b =3;
这个HAVING既过滤了普通分组(排除汇总行)又限定b=3,而WHERE只能先过滤b=3,无法区分分组类型;如果逻辑是HAVING GROUPING(b)=1(只保留汇总行),WHERE完全无法实现等价效果。
总结
仅在简单GROUP BY(无扩展分组语法)、HAVING仅过滤GROUP BY键列的场景下,两者等价。一旦涉及扩展分组生成的汇总行,或者结合分组相关的函数(如GROUPING()),HAVING和WHERE的功能就会出现差异。
内容的提问来源于stack exchange,提问作者Mason Wheeler

