SELECT查询返回相同的东西,无论是EXISTS还是NOT EXISTS - 为什么?

时间:2014-08-12 23:26:03

标签: sql postgresql where postgresql-9.2 exists

我正在调试以下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
    )
);

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

    如果count(*)(condition_1a AND condition_1b AND condition_1c) ,则
  1. True只能返回非零行数
  2. condition_1c仅在(condition_2a=True AND condition_2b=False)
  3. 时为True
  4. 如果整个表达式返回非零行数,则condition_2b必须为FalseNOT EXISTSTrue
  5. 如果整个表达式返回非零行数,则将NOT EXISTS更改为EXISTS会导致整个表达式返回0
  6. 我正在使用PostgreSQL v9.2.8

2 个答案:

答案 0 :(得分: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_id name 可能分别链接到不同的博客和标签。在没有看到实际数据的情况下,我建议通过指定select_2.blog_id = select_3.blog_id(或类似),可能需要在select_2和select_3之间建立更紧密的链接。

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

  1. 如果org = 22中有一个标签,那么来自org = 4的不是 blog_tag,其中有一个活跃的' foo'博客具有相同的优先级,标签具有相同的名称。
  2. 如果org = 22中有一个标记,并且org = 4中有 IS A blog_tag,其中有一个活跃的' foo'博客具有相同的优先级,标签具有相同的名称。
  3. 如果情况1. Select_2将返回一堆行,其中标签,博客和blog_tag的组合排除了匹配条件。可能是标签a,b,d&例如。

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

    无论哪种方式,Select_2都找到了一些东西,只需要返回所有结果。

    注意:查询之间的别名非常临时,很难看出每个查询中特定表的使用位置。 blog_tags在select_1或select_3中没有别名,博客的别名为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
        )
    );
    

答案 1 :(得分:1)

关于你的解释"在问题更新中添加:

  如果1.count(*)

(condition_1a AND condition_1b AND condition_1c) True只能返回非零行数

count(*)永远不会返回NULL,但会在找到行时返回零(0)。这使得它在标准聚合函数中很特殊。 Per documentation:

  

应该注意除count外,这些函数返回a   没有选择行时为空值。

你可能意味着:

count(*) can only return a non-zero number of rows

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

正确的句子是:

count(*)只有(condition_1a AND condition_1b AND condition_1c)评估为TRUE 的一个或多个输入行才能返回非零数字

  

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

时才为真

正确。

  

3. 如果整个表达式返回非零行数,则condition_2b必须为False才能NOT EXISTS成为True

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

  

4. 如果整个表达式返回非零行数,则将NOT EXISTS更改为EXISTS会导致整个表达式返回{{ 1}}。

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

鉴于您的建议是基于错误的假设,我建议您重新评估您的发现,如果可以,请使用SSCCE 返回。