ili*_*rit 507 sql sql-server notin
哪个查询更快?
不存在:
SELECT ProductID, ProductName
FROM Northwind..Products p
WHERE NOT EXISTS (
SELECT 1
FROM Northwind..[Order Details] od
WHERE p.ProductId = od.ProductId)
Run Code Online (Sandbox Code Playgroud)
或者不是:
SELECT ProductID, ProductName
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
SELECT ProductID
FROM Northwind..[Order Details])
Run Code Online (Sandbox Code Playgroud)
查询执行计划表明他们都做同样的事情.如果是这种情况,这是推荐的形式?
这基于NorthWind数据库.
[编辑]
刚刚找到这篇有用的文章:http: //weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
我想我会坚持使用NOT EXISTS.
Mar*_*ith 662
我总是默认NOT EXISTS
.
该执行计划可以是相同的时刻,但如果任一列在未来改变,以允许NULL
S上的NOT IN
版本需要做更多的工作(即使没有NULL
s为实际存在的数据)和语义NOT IN
如果NULL
小号都存在无论如何都不太可能是你想要的.
如果没有Products.ProductID
或[Order Details].ProductID
允许NULL
S中的NOT IN
将被同等对待下面的查询.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Run Code Online (Sandbox Code Playgroud)
具体计划可能会有所不同,但对于我的示例数据,我得到以下内容.
一个相当普遍的误解似乎是相关的子查询与连接相比总是"坏".它们当然可以强制嵌套循环计划(逐行评估子查询),但此计划包括反半连接逻辑运算符.反半连接不限于嵌套循环,但可以使用散列或合并(如本示例中所示)连接.
/*Not valid syntax but better reflects the plan*/
SELECT p.ProductID,
p.ProductName
FROM Products p
LEFT ANTI SEMI JOIN [Order Details] od
ON p.ProductId = od.ProductId
Run Code Online (Sandbox Code Playgroud)
如果[Order Details].ProductID
是NULL
-able则查询成为
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
AND NOT EXISTS (SELECT *
FROM [Order Details]
WHERE ProductId IS NULL)
Run Code Online (Sandbox Code Playgroud)
这样做的原因是,如果[Order Details]
包含任何NULL
ProductId
s 的正确语义是不返回任何结果.请参阅额外的反半连接和行计数假脱机以验证添加到计划中的此情况.
如果Products.ProductID
也变为成为NULL
-able,则查询变为
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
AND NOT EXISTS (SELECT *
FROM [Order Details]
WHERE ProductId IS NULL)
AND NOT EXISTS (SELECT *
FROM (SELECT TOP 1 *
FROM [Order Details]) S
WHERE p.ProductID IS NULL)
Run Code Online (Sandbox Code Playgroud)
其原因之一是因为NULL
Products.ProductId
不应该在返回的结果只是如果NOT IN
子查询是在所有返回任何结果(即[Order Details]
表是空的).它应该在哪种情况下.在我的样本数据计划中,这是通过添加另一个反半连接来实现的,如下所示.
这一点的效果显示在Buckley已经链接的博客文章中.在该示例中,逻辑读取的数量从大约400增加到500,000.
另外,单个NULL
可以将行计数减少到零的事实使得基数估计非常困难.如果SQL Server认为会发生这种情况,但事实上NULL
数据中没有行,执行计划的其余部分可能会更糟糕,如果这只是更大查询的一部分,不恰当的嵌套循环导致重复执行昂贵的子例如树.
但是,这不是NOT IN
on NULL
-able列的唯一可能的执行计划.本文展示了另一个针对AdventureWorks2008
数据库的查询.
对于NOT IN
上一NOT NULL
列或NOT EXISTS
反对任何一个可以为空或非空列它提供了以下方案.
当列变为NULL
-able时,NOT IN
计划现在看起来像
它为计划添加了额外的内连接运算符.这里解释了这个装置.将所有先前的单个相关索引搜索转换为Sales.SalesOrderDetail.ProductID = <correlated_product_id>
每个外行的两个搜索.另外一个是开启的WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
因为这是一个反半连接,如果那个返回任何行,第二次搜索将不会发生.但是,如果Sales.SalesOrderDetail
不包含任何NULL
ProductID
s,它将使所需的查找操作数量翻倍.
buc*_*ley 78
还要注意,当涉及到null时,NOT IN不等同于NOT EXISTS.
这篇文章很好地解释了它
http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/
当子查询返回一个null时,NOT IN将不匹配任何行.
通过查看NOT IN操作实际含义的细节可以找到原因.
让我们说,为了说明的目的,表中有4行叫做t,有一个名为ID的列,其值为1..4
Run Code Online (Sandbox Code Playgroud)WHERE SomeValue NOT IN (SELECT AVal FROM t)
相当于
Run Code Online (Sandbox Code Playgroud)WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1) AND SomeValue != (SELECT AVal FROM t WHERE ID=2) AND SomeValue != (SELECT AVal FROM t WHERE ID=3) AND SomeValue != (SELECT AVal FROM t WHERE ID=4)
让我们进一步说AVal是NULL,其中ID = 4.因此,!=比较返回UNKNOWN.AND的逻辑真值表表明UNKNOWN和TRUE是UNKNOWN,UNKNOWN和FALSE是FALSE.没有值可以与UNKNOWN进行AND运算以产生结果TRUE
因此,如果该子查询的任何行返回NULL,则整个NOT IN运算符将计算为FALSE或NULL,并且不会返回任何记录
Joh*_*kin 23
如果执行计划员说他们是相同的,那么他们就是一样的.使用任何一个会使你的意图更明显 - 在这种情况下,第二个.
Jam*_*ran 14
实际上,我相信这将是最快的:
SELECT ProductID, ProductName
FROM Northwind..Products p
outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
Run Code Online (Sandbox Code Playgroud)
小智 10
我有一个表有大约120,000条记录,需要在其他四个表中选择那些不存在(与varchar列匹配)的表,行数约为1500,4000,40000,200.所有涉及的表都有唯一索引关于Varchar
专栏.
NOT IN
花了大约10分钟,NOT EXISTS
花了4秒钟.
我有一个递归查询,可能有一些未调整的部分,可能有10分钟的贡献,但另一个选项花了4秒解释,至少对我来说,这NOT EXISTS
是更好或至少那个IN
,EXISTS
并不完全相同,总是值得一个在进行代码之前检查.
让\xe2\x80\x99s 假设我们的数据库中有以下两个表,它们形成一对多表关系。
\n\n该student
表是父表,是student_grade
子表,因为它有一个 Student_id 外键列引用学生表中的 id 主键列。
其中student table
包含以下两条记录:
ID | 名 | 姓 | 录取分数 |
---|---|---|---|
1 | 爱丽丝 | 史密斯 | 8.95 |
2 | 鲍勃 | 约翰逊 | 8.75 |
并且,该student_grade
表存储学生获得的成绩:
ID | 班级名称 | 年级 | 学生卡 |
---|---|---|---|
1 | 数学 | 10 | 1 |
2 | 数学 | 9.5 | 1 |
3 | 数学 | 9.75 | 1 |
4 | 科学 | 9.5 | 1 |
5 | 科学 | 9 | 1 |
6 | 科学 | 9.25 | 1 |
7 | 数学 | 8.5 | 2 |
8 | 数学 | 9.5 | 2 |
9 | 数学 | 9 | 2 |
10 | 科学 | 10 | 2 |
11 | 科学 | 9.4 | 2 |
假设\xe2\x80\x99s 表示我们想要获取数学课上获得 10 分的所有学生。
\n如果我们只对学生标识符感兴趣,那么我们可以运行如下查询:
\nSELECT\n student_grade.student_id\nFROM\n student_grade\nWHERE\n student_grade.grade = 10 AND\n student_grade.class_name = \'Math\'\nORDER BY\n student_grade.student_id\n
Run Code Online (Sandbox Code Playgroud)\n但是,应用程序有兴趣显示 a 的全名student
,而不仅仅是标识符,因此我们student
还需要表中的信息。
为了过滤student
数学成绩为 10 的记录,我们可以使用 EXISTS SQL 运算符,如下所示:
SELECT\n id, first_name, last_name\nFROM\n student\nWHERE EXISTS (\n SELECT 1\n FROM\n student_grade\n WHERE\n student_grade.student_id = student.id AND\n student_grade.grade = 10 AND\n student_grade.class_name = \'Math\'\n)\nORDER BY id\n
Run Code Online (Sandbox Code Playgroud)\n运行上面的查询时,我们可以看到仅选择了 Alice 行:
\nID | 名 | 姓 |
---|---|---|
1 | 爱丽丝 | 史密斯 |
外部查询选择student
我们感兴趣的行列返回给客户端。但是,WHERE 子句将 EXISTS 运算符与关联的内部子查询一起使用。
如果子查询至少返回一条记录,则 EXISTS 运算符返回 true;如果没有选择任何行,则 EXISTS 运算符返回 false。数据库引擎不必完全运行子查询。如果匹配单个记录,则 EXISTS 运算符返回 true,并选择关联的其他查询行。
\n内部子查询是相关的,因为表的 Student_id 列student_grade
与外部 Student 表的 id 列匹配。
让\xe2\x80\x99s 考虑我们要选择所有成绩不低于9的学生。为此,我们可以使用NOT EXISTS,它否定了EXISTS运算符的逻辑。
\n因此,如果基础子查询没有返回记录,则 NOT EXISTS 运算符将返回 true。但是,如果内部子查询匹配单个记录,则 NOT EXISTS 运算符将返回 false,并且可以停止子查询的执行。
\n要匹配所有没有关联的student_grade且值低于9的学生记录,我们可以运行以下SQL查询:
\nSELECT\n id, first_name, last_name\nFROM\n student\nWHERE NOT EXISTS (\n SELECT 1\n FROM\n student_grade\n WHERE\n student_grade.student_id = student.id AND\n student_grade.grade < 9\n)\nORDER BY id\n
Run Code Online (Sandbox Code Playgroud)\n运行上面的查询时,我们可以看到只有 Alice 记录被匹配:
\nID | 名 | 姓 |
---|---|---|
1 | 爱丽丝 | 史密斯 |
所以,使用SQL EXISTS和NOT EXISTS运算符的好处是,只要找到匹配的记录就可以停止内部子查询的执行。
\n在您的具体示例中它们是相同的,因为优化器已经弄清楚您尝试做的是两个示例中相同的内容.但有可能的是,在非平凡的例子中,优化器可能不会这样做,并且在这种情况下,有理由有时会优先选择其中一个.
NOT IN
如果您在外部选择中测试多行,则应该首选.NOT IN
可以在执行开始时评估语句中的子查询,并且可以针对外部选择中的每个值检查临时表,而不是每次重新运行子NOT EXISTS
语句,如语句所需.
如果子查询必须与外部选择相关联,则NOT EXISTS
可能是优选的,因为优化器可能发现一种简化,该简化阻止创建任何临时表以执行相同的功能.
我在用
SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)
Run Code Online (Sandbox Code Playgroud)
并发现它给出了错误的结果(错误是指没有结果)。由于TABLE2.Col1中为NULL。
将查询更改为
SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)
Run Code Online (Sandbox Code Playgroud)
给了我正确的结果。
从那时起,我开始在每个地方都使用NOT EXISTS。
归档时间: |
|
查看次数: |
1384239 次 |
最近记录: |