在 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...) 根据排序和单列引用,这似乎不适合HAVINGHAVING 子句用于过滤某种聚合表达式。如果您只想根据列值进行过滤,则需要WHERE NOT (flag_1 = 1 ...) 您正在寻找WHERE 而不是NOT HAVING。对于您正在测试的条件,没有理由使用 HAVINGNOT 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_1NULL 的所有行都没有保留。据我所知NULL != 1,所以那些行应该在那里? @mach128x 与NULL 的任何比较都是错误的。 1 = NULL 是假的; 1 != NULL 也是错误的。我的回答中对此进行了解释。 在我的具体应用程序中,flag_1flag_2flag_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_1flag_2 都等于 1 但 flag_3 null 的行不会显示在您的结果中。

【讨论】:

在我的具体应用程序中,flag_1flag_2flag_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中具有行的聚合

如何对具有聚合函数的 Oracle 查询进行分组

转置和聚合Oracle列数据

在 Oracle 中使用增强的聚合报告功能解决问题

如果之前在组中没有看到值,则聚合值 - SQL / ORACLE

oracle聚合函数XMLAGG用法简介