Sql 不存在vs不在
我正在优化一些SQL查询(这可能是我最近发布的一个问题的第2部分),并将一些NOT IN替换为NOT EXISTS谓词 我认为这样做的主要好处是,使用notexists,您可以得到这样的好处:当找到单个匹配项时,语句将终止,但如果不使用counting子查询,则必须执行完整的表扫描,这是对的吗 如果所选数据包含空值,则NOT IN似乎还需要额外的工作,这是否正确 在我在proc中实现它们之前,我需要确保在这两种情况下,第二个语句比第一个语句(以及功能上等效的语句)更好: 案例1:Sql 不存在vs不在,sql,sql-server,optimization,plsql,Sql,Sql Server,Optimization,Plsql,我正在优化一些SQL查询(这可能是我最近发布的一个问题的第2部分),并将一些NOT IN替换为NOT EXISTS谓词 我认为这样做的主要好处是,使用notexists,您可以得到这样的好处:当找到单个匹配项时,语句将终止,但如果不使用counting子查询,则必须执行完整的表扫描,这是对的吗 如果所选数据包含空值,则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
)
Cheers
不存在
运行查询,对行进行计数,如果计数=0,则返回true
notin
运行查询,对结果进行迭代,将给定值与结果进行比较,如果不匹配,则返回true
通常第一种方法要快得多
当然,您必须注意从周围的查询中包含哪些列/值到子查询中,因为这可能会导致子查询运行N次(外部结果集中的每行一次)
还有另一种方法:使用
外部连接将表A连接到表B,并检查表B中的列是否为NULL
。这将只运行一次子查询。它只能在更简单的情况下使用(不适用于多个表联接链) 你是对的,空值有很大的不同。notin
查询检查每个元素是否完全不匹配。与null进行比较不会产生最终结果。因此,如果子查询包含null,则不会将其视为“不在”
”中
这种行为的非直观的副作用是,不在
实际上不是
中的的对立面
不存在
查询不存在此问题
对于哪一种性能更好,我会犹豫是否做任何笼统的陈述,因为这通常取决于发生了什么样的优化。这就是为什么如果你关心绩效,那么找出执行计划是很重要的。正如你正确地说的,这两者是不同的。如果
中不为的项的子查询包含NULL
则不会返回任何结果,因为没有任何项等于NULL
,也没有任何项不等于NULL
(甚至不等于NULL)
假设您使用这两种方法获得相同的结果,那么只要您在in
语句中处理NULL
值,这两种方法之间就没有区别。优化器非常聪明,知道如果消除了NULL
值,或者使用不可为NULL的列,这两个列是相同的,所以使用相同的反半联接
考虑以下两个表:
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);
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);
因为优化程序知道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);
而不存在将更有效率。然而,这两种方法(本质上)再次产生相同的执行计划:
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);
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);
所有这些查询和示例数据都已打开
编辑
要真正回答您的问题:
如果跟踪的会话id
在数据中是不可为空的列,则案例1
的性能将与不在或不存在的情况相同。转换
,或者在IN语句中添加跟踪的会话id不为空的。如果列不可为null,并且不排除null值,则性能将不相同,并且假设不存在null不存在
将执行得更好,如果不存在null,则结果将不相同,因此性能不具有可比性
案例2的示例数据实际上让我很惊讶,我假设这不会优化为反半联接
,并且已经写了一个答案,说了这么多,但就在保存编辑之前,我认为我最好检查一下,并惊讶地看到:
SELECT *
FROM T
WHERE ( SELECT COUNT(*)
FROM T3
WHERE T.ID = T3.ID
) = 0;
与不存在时的优化完全相同
。因此,乐观主义者似乎比我想象的更聪明,如果你希望计数不是0,它只会生成一个不同的计划
执行计划是否会告诉您是否不在执行表扫描?我个人会看一下计划中关于性能/IO统计信息的内容,而不是其他内容。不幸的是,我无法(轻松地)对数据源运行这些存储过程,以获得一个以Oracle 10g IN/开始的查询计划,而不是显式转换为EXISTS/NOT。“不存在”与“不存在”一样有效。但是,如果子查询可能返回空值,则不要使用not IN。not IN的可能重复项不太明显,因为not EXISTS
可以在找到单个匹配项时立即终止,同样,not IN
可以优化为执行左反半联接,该联接也会在找到第一个匹配项时终止,并且可以使用索引不存在
和不在
都优化为使用左反半联接
。这是非常错误的。正如@ChrisChilvers所说,它们都作为左反半联接运行。区别在于空值的大小handled@ChrisChilvers很想知道。你能提供一些参考吗?这就是我的想法(行计数vs迭代),不是吗?如果你要求@ChrisChilvers,请看一下执行计划。在mssql 2005中,由于许多人在IF语句中使用该语法,他们增加了对count(*)=0
和count(*)>0
的优化。虽然存在是更好的选择,因为技术上是正确的。