我正在优化一些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)
干杯
答案 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 IN
或NOT EXISTS
相同,如果tracked_session_id
是data.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,它只会生成一个不同的计划。
答案 1 :(得分:2)
你是对的,与null有很大的不同。 NOT IN
查询检查每个元素是否明确不匹配。与null的比较不会产生明确的结果。因此,如果您的子查询包含null,则不会将任何内容视为“NOT IN
”。
此行为的不直观副作用是NOT IN
实际上与IN
不相反。
NOT EXISTS
查询没有此问题。
我会犹豫是否做出任何关于哪个表现更好的一揽子声明,因为这通常取决于发生什么样的优化。这就是为什么如果你关心性能,能够找到执行计划是很重要的。
答案 2 :(得分:-2)
NOT EXISTS
运行查询,计算行数,如果count == 0,则返回true
。
NOT IN
运行查询,迭代结果,将给定值与结果进行比较,如果没有匹配,则返回true
通常第一种方法要快得多。
当然,您必须注意从周围的查询中包含到子查询中的列/值,因为这可能导致子查询运行N次(对于外部结果集中的每一行一次)。
还有另一种方法:将表A与OUTER JOIN
连接到表B,并检查表B中的列是否为NULL
。这将只运行一次子查询。它只能在更简单的情况下使用(不适用于多个表连接链)。