在 Oracle 中具有聚合的 NOT HAVING(逆 HAVING)子句
Posted
技术标签:
【中文标题】在 Oracle 中具有聚合的 NOT HAVING(逆 HAVING)子句【英文标题】:NOT HAVING (inverse HAVING) clause with aggregation in Oracle 【发布时间】:2020-02-19 19:20:52 【问题描述】:我正在尝试使用HAVING
子句过滤GROUP BY CUBE
的结果。但是我需要保留不满足条件组合的行。
我凭直觉尝试过:
SELECT [...]
FROM [...]
NOT HAVING (flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1)
GROUP BY CUBE [...]
遗憾的是,Oracle 无法将 NOT HAVING
识别为有效语法。
从数学的角度来看,反转每个单独的条件不会产生相同的结果:
HAVING (flag_1 != 1 AND flag_2 != 1 AND flag_3 != 1)
如何实现NOT HAVING
的逻辑等效?
注意:我发现了一个与 Microsoft Access 相关的现有问题,但它特定于 Microsoft Access,并且目标不一样,因此提出了这个新问题。
【问题讨论】:
First 是偶数HAVING (flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1)
有效吗?第二个从数学/SQL 的角度来看col = 1
反转是col != 1 or col IS NULL
- 三值逻辑
试试HAVING NOT (flag_1=1...)
根据排序和单列引用,这似乎不适合HAVING
。 HAVING
子句用于过滤某种聚合表达式。如果您只想根据列值进行过滤,则需要WHERE NOT (flag_1 = 1 ...)
。
您正在寻找WHERE
而不是NOT HAVING
。对于您正在测试的条件,没有理由使用 HAVING
或 NOT HAVING
。 SQL 教程可能有用。
@KenWhite 实际上我正在寻找过滤后聚合,而不是预聚合。因此HAVING
而不是WHERE
。
【参考方案1】:
HAVING 子句的数学逆运算要求您将 AND 更改为 OR,如果列可以为空,还需要进行空检查。
EG(如果可能为空):
HAVING (nvl(flag_1,1) != 1 OR NVL(flag_2,1) != 1 OR NVL(flag_3,1) != 1)
【讨论】:
我刚刚做了这个测试:HAVING flag_1 != 1
。我很惊讶flag_1
是NULL
的所有行都没有保留。据我所知NULL != 1
,所以那些行应该在那里?
@mach128x 与NULL
的任何比较都是错误的。 1 = NULL
是假的; 1 != NULL
也是错误的。我的回答中对此进行了解释。
在我的具体应用程序中,flag_1
、flag_2
和 flag_3
永远不会是 NULL
。但是,由于该子句在解析任何 NULL
时将无法提供预期的结果,因此在我自己的特定应用程序之外通常需要正确处理它们。这个答案和@MatthewMcPeak 的答案都在解决这个问题。我最终选择接受这个答案,因为我认为使用 NVL 更优雅。【参考方案2】:
我认为最简单的解决方案是将其更改为HAVING NOT ...
SELECT [...]
FROM [...]
HAVING NOT (flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1)
GROUP BY CUBE [...]
但我经常发现NOT (...)
不直观;作为替代方案,@MichaelBroughton 的回答解释了如何将逻辑从NOT (x AND y)
反转为NOT x OR NOT y
【讨论】:
这是最优雅和最简单的解决方案恕我直言。我不知道为什么我没有早点想到它,把NOT
after 放在HAVING
,而不是before。虽然我没有发现 NOT
通常不直观,但我想它实际上在特定的语义上下文中 is 有点不直观。尽管如此,在我的书中,优雅和不使用附加功能是一个胜利。
优雅,但不能正确处理空值,除非我弄错了。【参考方案3】:
这与 Michael Broughton 的回答相同,我讨厌重复,但我认为它可以更清楚。如果他想将其中任何内容纳入他的答案,我很乐意删除此内容。
要确定 NOT (boolean_expression) 的逻辑等价物,您可以应用德摩根定律。见:https://en.wikipedia.org/wiki/De_Morgan%27s_laws
基本上,您颠倒每个术语的逻辑并将所有 AND 更改为 OR,并将所有 OR 更改为 AND。所以,
NOT (A AND B AND C) ==> (NOT A OR NOT B OR NOT C)
但您也需要跟踪空值。流程如下:
从...开始
HAVING NOT (flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1)
首先,添加关于起始表达式中存在的 NULL 的隐含假设。例如,如果flag_1=1
,那么它当然不是NULL。
HAVING NOT (flag_1 = 1 AND flag_1 IS NOT NULL
AND flag_2 = 1 AND flag_2 IS NOT NULL
AND flag_3 = 1 AND flag_3 IS NOT NULL)
现在,应用德摩根定律的第一部分并颠倒每个术语的逻辑。因此,例如,flag_1 = 1
变为 flag_1 != 1
...
HAVING (flag_1 != 1 AND flag_1 IS NULL
AND flag_2 != 1 AND flag_2 IS NULL
AND flag_3 != 1 AND flag_3 IS NULL)
最后,应用德摩根定律的第二部分并切换所有 AND->OR,反之亦然...
HAVING (flag_1 != 1 OR flag_1 IS NULL
OR flag_2 != 1 OR flag_2 IS NULL
OR flag_3 != 1 OR flag_3 IS NULL)
这就是你的答案。
NOT (flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1)
怎么样?
这是行不通的,因为它没有像预期的那样处理空值。在 Oracle 中,任何与 null 的比较都是错误的。所以,
(1 = NULL) ... false
NOT (1 = NULL) ... also false
因此,flag_1
和 flag_2
都等于 1 但 flag_3
null 的行不会显示在您的结果中。
【讨论】:
在我的具体应用程序中,flag_1
、flag_2
和flag_3
永远不会是NULL
。但是,由于该子句在解析任何NULL
时将无法提供预期的结果,因此在我自己的特定应用程序之外通常需要正确处理它们。谢谢。【参考方案4】:
嗯,我考虑过基于CASE
采用一种类似的方法,我有时会在WHERE
中使用这种方法。
CASE
似乎也可以在 HAVING
内部工作!
解决方案:
SELECT [...]
FROM [...]
HAVING
CASE
WHEN flag_1 = 1 AND flag_2 = 1 AND flag_3 = 1
THEN 0
ELSE 1
END = 1
GROUP BY CUBE [...]
【讨论】:
如果在解析任何 NULL 时,将特定逻辑与HAVING
一起使用将无法提供预期结果。有关更强大的解决方案,请参阅 @Michael Brougton 和 @Matthew McPeak 的答案。如果没有要解析的 NULL
标志,此解决方案仍然有效。以上是关于在 Oracle 中具有聚合的 NOT HAVING(逆 HAVING)子句的主要内容,如果未能解决你的问题,请参考以下文章
sql 从字符串..with毫秒的Oracle日期时间戳格式化。该示例还使用listagg在oracle中具有行的聚合