使用LEFT JOIN或NOT EXISTS之间的最佳实践


67

在使用LEFT JOIN或NOT EXISTS格式之间有最佳实践吗?

相对于另一个使用什么有什么好处?

如果没有,则应首选?

SELECT *
FROM tableA A
LEFT JOIN tableB B
     ON A.idx = B.idx
WHERE B.idx IS NULL

SELECT *
FROM tableA A
WHERE NOT EXISTS
(SELECT idx FROM tableB B WHERE B.idx = A.idx)

我在Access中使用SQL Server数据库查询。


2
顺便说一句,看似完全相同的方法WHERE A.idx NOT IN (...) 相同的,由于三价的行为NULL(即NULL不等于NULL(也不不等),因此,如果您有任何 NULLtableB你会得到意想不到的结果!)
Elaskanator

Answers:


58

最大的区别不是联接与不存在,而是(按书面说明)SELECT *

在第一个示例中,您同时A和中获取了所有列B,而在第二个示例中,您仅从中获取了列A

在SQL Server中,在一个非常简单的示例中,第二种变体要快一些:

创建两个示例表:

CREATE TABLE dbo.A
(
    A_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);

CREATE TABLE dbo.B
(
    B_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);
GO

在每个表中插入10,000行:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

从第二个表中删除每第5行:

DELETE 
FROM dbo.B 
WHERE B_ID % 5 = 1;

SELECT COUNT(*) -- shows 10,000
FROM dbo.A;

SELECT COUNT(*) -- shows  8,000
FROM dbo.B;

执行两个测试SELECT语句变体:

SELECT *
FROM dbo.A
    LEFT JOIN dbo.B ON A.A_ID = B.B_ID
WHERE B.B_ID IS NULL;

SELECT *
FROM dbo.A
WHERE NOT EXISTS (SELECT 1
    FROM dbo.B
    WHERE b.B_ID = a.A_ID);

执行计划:

在此处输入图片说明

第二个变体不需要执行过滤操作,因为它可以使用左反半连接运算符。


23

从逻辑上讲,它们是相同的,但是NOT EXISTS更接近于您所要求的AntiSemiJoin,通常是首选。它还突出显示了您无法访问B中的列,因为它仅用作过滤器(而不是使它们具有NULL值)。

许多年前(SQL Server 6.0 ish)LEFT JOIN速度更快,但很长一段时间以来并非如此。这些天,NOT EXISTS速度略快。


Access中最大的影响是该JOIN方法必须在过滤之前完成连接,然后在内存中构造连接集。使用NOT EXISTS它检查行,但不为列分配空间。另外,一旦找到行,它就会停止寻找。在Access中,性能会有较大差异,但通常的经验法则是NOT EXISTS速度会稍快一些。由于涉及更多因素,我不太愿意说这是“最佳实践”。


6

我注意到的一个例外是使用Linked ServersNOT EXISTS具有优越性(但略有优势)。LEFT JOIN ... WHERE IS NULL

通过检查执行计划,可以发现NOT EXISTS操作员以嵌套循环的方式执行。从而在每行的基础上执行(我认为这很有意义)。

展示此行为的示例执行计划: 在此处输入图片说明


1
链接服务器对于这种事情是残酷的。解决该问题的一种可能方法是,使用简单INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yNOT EXISTS (...)子句然后针对数据库的该临时副本运行子句,从而通过链接的服务器链接复制远程数据。
Max Vernon

2
现在有点害羞,可以在我的帖子中得到Max Vernon的回应!放开头。您提到这很有趣,因为我已经多次使用该精确方法来充分利用那些跨服务器的情况。
robopim

1
干杯,@ pimbrouwers-感谢您的友好评论!
马克斯·弗农

5

通常,引擎将基于以下内容创建执行计划:

  1. A和B中的行数
  2. A和/或B上是否有索引。
  3. 预期结果行数(和中间行)
  4. 输入查询的形式(即您的问题)

对于(4):

“不存在”计划鼓励在表B上进行基于搜索的计划。当表A小且表B大(并且B上存在索引)时,这是一个不错的选择。

当表A很大或表B很小或B上没有索引并返回大结果集时,“ antijoin”计划是一个不错的选择。

但是,这只是一种“鼓励”,就像加权输入一样。强(1),(2),(3)通常是(4)模拟的选择。

(忽略了示例由于*而返回不同的列的效果,@ MaxVernon回答。)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.