不存在而不是存在

时间:2013-01-23 11:49:08

标签: sql sql-server optimization plsql

我正在优化一些SQL查询(这可以被认为是我最近发布的问题的第2部分)并用NOT EXISTS谓词替换了一些NOT IN

我认为这样做的主要好处是,使用NOT EXISTS可以获得声明在找到单个匹配时终止的好处,但是对于计数子查询而言,NOT IN将需要完整表扫描?

如果选择的数据包含NULL,NOT IN似乎还需要额外的工作,这是正确的吗?

在我在proc中实现它们之前,我需要确保第二个语句比第一个语句(和功能上等效的)更好:

案例1:

        --exclude sessions that were tracked as part of a conversion during the last response_time minutes
        -- AND session_id NOT IN (SELECT DISTINCT tracked_session_id    
        --                              FROM data.conversions WITH (NOLOCK)
        --                              WHERE client_id = @client_id
        --                              AND utc_date_completed >= DATEADD(minute, (-2) * cy.response_time, @date)
        --                              AND utc_date_completed <= @date     
        --                              AND utc_date_clicked <= @date)

        AND NOT EXISTS (SELECT 1
                            FROM data.conversions WITH (NOLOCK)
                            WHERE client_id = @client_id
                            AND utc_date_completed >= DATEADD(minute, (-2) * cy.response_time, @date)
                            AND utc_date_completed <= @date
                            AND utc_date_clicked <= @date
                            AND data.conversions.tracked_session_id = d.session_id
        )

案例2:

        -- NOT EXISTS vs full table scan with COUNT(dashboard_id)                                   
        -- AND (SELECT COUNT(dashboard_id)
        --          FROM data.dashboard_responses WITH(NOLOCK)
        --          WHERE session_id = d.session_id
        --          AND cycle_id = cy.id
        --          AND client_id = @client_id) = 0

        AND NOT EXISTS(SELECT 1
                            FROM data.dashboard_responses
                            WHERE session_id = d.session_id
                            AND cycle_id = cy.id
                            AND client_id = @client_id)

干杯

3 个答案:

答案 0 :(得分:5)

正如你正确地说这两个是不同的东西。如果不是IN的项的子查询包含NULL,则不会返回任何结果,因为没有任何内容等于NULL且没有任何内容不等于NULL(甚至不是NULL)。

假设您使用两者来实现相同的结果,只要您在NULL语句中处理IN值,两者之间就没有区别。优化器足够聪明,知道消除了NULL值,或者使用不可为空的列,两者是相同的,所以使用相同的ANTI SEMI JOIN

考虑这两个表:

CREATE TABLE T (ID INT NOT NULL PRIMARY KEY);
CREATE TABLE T2 (ID INT NOT NULL PRIMARY KEY);

这两个查询获得完全相同的执行计划:

SELECT  *
FROM    T
WHERE   ID NOT IN (SELECT ID FROM T2);

SELECT  *
FROM    T
WHERE   NOT EXISTS (SELECT ID FROM T2 WHERE T.ID = T2.ID);

因为优化器知道T2.ID是一个不可为空的列。第三个表:

CREATE TABLE T3 (ID INT);

其中ID列既没有索引也没有可空,这两个查询呈现非常不同的执行计划:

SELECT  *
FROM    T
WHERE   ID NOT IN (SELECT ID FROM T3);

SELECT  *
FROM    T
WHERE   NOT EXISTS (SELECT ID FROM T3 WHERE T.ID = T3.ID);

和NOT EXISTS会更有效率。然而,这两个再次产生(基本上)相同的执行计划:

SELECT  *
FROM    T
WHERE   ID NOT IN (SELECT ID FROM T3 WHERE T3.ID IS NOT NULL);

SELECT  *
FROM    T
WHERE   NOT EXISTS (SELECT ID FROM T3 WHERE T.ID = T3.ID);

所有这些查询和示例数据都在SQL Fiddle

修改

要真正回答你的问题:

案例1 NOT INNOT EXISTS相同,如果tracked_session_iddata.conversions中不可为空的列,或者您添加In语句中的WHERE tracked_Session_id IS NOT NULL。如果列不可为空并且您不排除空值,则性能将不相同,并且假设没有空值NOT EXISTS将表现更好,如果没有空值,结果将不是同样如此表现无法比较。

案例2 实际上让我对示例数据感到惊讶,我曾假设这不会被优化为ANTI SEMI JOIN,并且已经写了一个答案,但是在保存之前编辑我认为我最好检查,并惊讶地看到这个:

SELECT  *
FROM    T
WHERE   (   SELECT  COUNT(*) 
            FROM    T3
            WHERE   T.ID = T3.ID
        ) = 0;

优化与NOT EXISTS完全相同。所以看起来优化器比我想象的更聪明,如果你想让计数不是0,它只会生成一个不同的计划。

SQL Fiddle for Case 2

答案 1 :(得分:2)

你是对的,与null有很大的不同。 NOT IN查询检查每个元素是否明确不匹配。与null的比较不会产生明确的结果。因此,如果您的子查询包含null,则不会将任何内容视为“NOT IN”。

See this SQL Fiddle example.

此行为的不直观副作用是NOT IN实际上与IN不相反。

NOT EXISTS查询没有此问题。

我会犹豫是否做出任何关于哪个表现更好的一揽子声明,因为这通常取决于发生什么样的优化。这就是为什么如果你关心性能,能够找到执行计划是很重要的。

答案 2 :(得分:-2)

NOT EXISTS运行查询,计算行数,如果count == 0,则返回true

NOT IN运行查询,迭代结果,将给定值与结果进行比较,如果没有匹配,则返回true

通常第一种方法要快得多。

当然,您必须注意从周围的查询中包含到子查询中的列/值,因为这可能导致子查询运行N次(对于外部结果集中的每一行一次)。

还有另一种方法:将表A与OUTER JOIN连接到表B,并检查表B中的列是否为NULL。这将只运行一次子查询。它只能在更简单的情况下使用(不适用于多个表连接链)。