无论是 EXISTS 还是 NOT EXISTS,SELECT 查询都会返回相同的结果——为啥?

Posted

技术标签:

【中文标题】无论是 EXISTS 还是 NOT EXISTS,SELECT 查询都会返回相同的结果——为啥?【英文标题】:SELECT query returns same thing whether EXISTS or NOT EXISTS -- why?无论是 EXISTS 还是 NOT EXISTS,SELECT 查询都会返回相同的结果——为什么? 【发布时间】:2014-08-12 23:26:02 【问题描述】:

我正在调试以下 SQL 语句,试图了解它的行为方式。

我惊讶地发现,如果我将 NOT EXISTS 更改为 EXISTS(并查询相同的、未更改的数据),我会得到完全相同的输出(这是行数,例如, 237)。怎么会这样?

我预计将NOT EXISTS 更改为仅EXISTS 会将其从返回正数行数(例如237)更改为返回0

SELECT count(*) FROM blog_tags
WHERE blog_tags.subscribed = true
AND blog_tags.special = true
AND EXISTS (
    SELECT 1
    FROM tags
    INNER JOIN blog_tags AS bt ON bt.tag_id = tags.id
    INNER JOIN blogs ON bt.blog_id = blogs.id
    WHERE blogs.org_id = 22
    AND NOT EXISTS ( /* Removing the "NOT" on this line has no effect */
        SELECT 1
        FROM blog_tags
        INNER JOIN tags AS tg ON blog_tags.tag_id = tg.id
        INNER JOIN blogs AS t ON blog_tags.blog_id = t.id
        WHERE t.org_id = 4
        AND t.active = true
        AND t.type = 'foo'
        AND t.priority_id = blogs.priority_id
        AND tg.name = tags.name
    )
);

我想知道我是否在概念上理解错误。将其重写为伪代码:

/* select_1 */
SELECT count(*) FROM sometable_1
WHERE condition_1a
AND condition_1b
AND EXISTS (
    /* condition_1c (the entire EXISTS inside these parentheses) */
    /* select_2 */
    SELECT 1
    FROM sometable2
    INNER JOIN join_expression_1a
    INNER JOIN join_expression_1b
    WHERE condition_2a
    AND NOT EXISTS ( /* Removing the "NOT" on this line has no effect */
        /* condition_2b (the entire NOT EXISTS inside these parentheses */
        /* select_3 */
        SELECT 1
        FROM sometable1
        INNER JOIN join_expression_2a
        INNER JOIN join_expression_2b
        WHERE condition_3a
        AND condition_3b
        AND condition_3c
        AND condition_3d
        AND condition_3e
    )
);

以下是我对上述伪代码的解释。这些解释是真的吗?

    如果(condition_1a AND condition_1b AND condition_1c)Truecount(*) 只能返回非零行数 condition_1c 仅在 (condition_2a=True AND condition_2b=False) 时为 True 如果整个表达式返回非零行数,则 condition_2b 必须是 False 才能使 NOT EXISTS 成为 True。 如果整个表达式返回非零行数,则将NOT EXISTS 更改为EXISTS 应该会导致整个表达式返回0

我正在使用PostgreSQL v9.2.8

【问题讨论】:

会不会是巧合?无论哪种方式,可能有相同数量的合格行。 重现问题的少量样本数据会很有趣且很有帮助。您确定连接条件或 WHERE 子句的其他部分不是问题的根源吗? 我猜org_id 永远不会是 22。 您是否尝试仅运行外部子查询,同时使用存在和不存在?您每次获得的行数是否相同? @GordonLinoff:如果org_id 永远不是 22,则计数为 0,而不是 237。 【参考方案1】:

至于您在问题更新中添加的“解释”:

1. 如果(condition_1a AND condition_1b AND condition_1c)Truecount(*) 只能返回非零行数

count(*) 从不返回 NULL,而是在现在找到行时返回零 (0)。这使得它在标准聚合函数中很特别。 Per documentation:

需要注意的是,除了count,这些函数都返回一个 未选择任何行时为空值。

你的意思可能是:

count(*) 只能返回非零行数

但你对事件的顺序也很模糊。为每个单独的输入行评估 WHEREJOIN 条件。聚合函数 count(*) 在之后评估。考虑SELECT 查询中的事件顺序:

Best way to get result count before LIMIT was applied

正确的句子是:

如果(condition_1a AND condition_1b AND condition_1c) 评估为TRUE对于一个或多个输入行count(*) 只能返回非零数字。

2. condition_1c 仅在 (condition_2a=True AND condition_2b=False) 时为真

正确。

3.如果整个表达式返回非零行数,则condition_2b 必须为False,以便NOT EXISTSTrue。 p>

参见 1。此外,如果您的 EXISTS 表达式不是常量(引用外部查询的列或调用任何 volatile 函数),EXISTS 表达式的结果可能不同每个输入行。

4.如果整个表达式返回非零行数,那么将NOT EXISTS 更改为EXISTS 应该会导致整个表达式返回0

不正确 - 如果EXISTS 表达式不是常量。请参阅 3。将 NOT EXISTS 更改为 EXISTS 可能会导致任何行数。

鉴于您建立在不正确的假设之上,我建议您重新评估您的发现并返回SSCCE如果可以的话

【讨论】:

【参考方案2】:
...
    AND NOT EXISTS ( /* Removing the "NOT" on this line has no effect */
        /* condition_2b (the entire NOT EXISTS inside these parentheses */
        /* select_3 */
        SELECT 1
        FROM sometable1
        INNER JOIN join_expression_2a
        INNER JOIN join_expression_2b
        WHERE condition_3a
        AND condition_3b
        AND condition_3c
        AND condition_3d    --- this condition links select_2 to select_3
        AND condition_3e    --- this condition links select_2 to select_3
    )
);

condition_3d 和 condition_3e 链接 select_2 和 select_3,但它是一个非常松散的耦合,因为 priority_idname 可能分别链接到不同的博客和标签。在没有看到实际数据的情况下,我建议通过指定 select_2.blog_id = select_3.blog_id (或类似的),可能需要在 select_2 和 select_3 之间建立更紧密的联系。

关于你的伪代码,我对代码所说的内容进行以下解释:

    如果存在来自 org=22 的标签,并且存在来自 org=4 的 IS NOT A blog_tag,其中活动的“foo”博客具有相同的优先级并且标签具有相同的名称。李>

在情况 1. Select_2 将返回一堆行,其中标签、博客和 blog_tag 的组合排除了匹配条件。例如,可能是标签 a、b、d 和 f。

在情况 2. Select_2 将返回一堆行,其中标签、博客和 blog_tag 的组合包含匹配条件。例如,可能是标签 c、e、g、k。

Select_2 无论如何都找到了一些东西,这就是它返回所有结果所需的全部内容。

注意:查询之间的别名是非常特殊的,很难看出每个查询中特定表的使用位置。 blog_tags 在 select_1 或 select_3 中都没有别名,blogs 的别名是 t。我建议在查询中的所有实例中始终对表使用相同的首字母缩写词(即 blog_tags 始终为 bt),然后为每个实例附加一个数字(即 bt1、bt2 等)。如下:

SELECT count(*) FROM blog_tags AS bt  -- add alias
WHERE bt.subscribed = true
AND bt.special = true
AND EXISTS (
    SELECT 1
    FROM tags AS t1   -- add alias
    INNER JOIN blog_tags AS bt1 ON bt1.tag_id = t1.id -- change alias
    INNER JOIN blogs AS b1 ON bt1.blog_id = b1.id  -- change alias
    WHERE b1.org_id = 22
    AND NOT EXISTS ( /* Removing the "NOT" on this line has no effect */
        SELECT 1
        FROM blog_tags AS bt2 -- change alias
        INNER JOIN tags AS t2 ON bt2.tag_id = t2.id -- change alias
        INNER JOIN blogs AS b2 ON bt2.blog_id = b2.id -- change alias
        WHERE b2.org_id = 4
        AND b2.active = true
        AND b2.type = 'foo'
        AND b2.priority_id = b1.priority_id
        AND t2.name = t1.name
    )
);

【讨论】:

以上是关于无论是 EXISTS 还是 NOT EXISTS,SELECT 查询都会返回相同的结果——为啥?的主要内容,如果未能解决你的问题,请参考以下文章

展开菜单以展开/折叠所有菜单,无论它们是展开还是关闭

无论对象是从 nib 加载还是以编程方式创建,执行初始化代码的正确方法是啥?

Dart/flutter 小部件测试,无论是按键还是文本都找不到文本小部件

Cordova:无论如何,是不是可以在 Javascript 中检测 iOS 应用程序是作为调试版还是作为发布版构建的?

Kolmogorov-Smirnov 检验中的假设检验 - 无论是临界值还是 p 值

无论管道是失败还是成功,都向 GitLab 报告 Tekton 管道状态(使用 gitlab-set-status 任务)