我经常使用MS Access作为临时数据处理工具。我注意到的一件事是,以某种方式使用子查询往往会对大表有非常差的性能。
例如,此查询执行效果不佳:
SELECT TOP 500 EmployeeID FROM Employee
WHERE EmployeeID NOT IN
(SELECT EmployeeID FROM LoadHistory
WHERE YearWeek = '2015-26');
此版本的同一查询效果很好:
SELECT TOP 500 EmployeeID FROM Employee
WHERE NOT EXISTS
(SELECT 1 FROM LoadHistory
WHERE YearWeek = '2015-26' AND
EmployeeID = Employee.EmployeeID);
同一查询的其他形式也表现良好:
SELECT TOP 500 Employee.EmployeeID
FROM Employee
LEFT JOIN
(SELECT EmployeeID FROM LoadHistory
WHERE YearWeek = '2015-26') q
ON Employee.EmployeeID = q.EmployeeID
WHERE q.EmployeeID IS NULL;
由于风格原因,我更喜欢第一种形式。我无法理解为什么优化器不会为第一个和第二个查询生成相同的计划。 ACE优化器在这里的表现有什么逻辑吗?有没有其他方法可以稍微重写第一个查询,以便优化器可以做得更好?
答案 0 :(得分:2)
NOT IN
和NOT EXISTS
非常相似。 。 。但不太一样。
NOT IN
的语义指定如果任何值为NULL
,它永远不会返回true。这意味着子查询必须验证这是真的。
我的猜测是,这解释了不同的优化方案。这也是我更喜欢NOT EXISTS
到NOT IN
的原因。 NOT EXISTS
在处理子查询中的NULL
值时更直观。
注意:使用相关子查询时,总是限定列名:
SELECT TOP 500 EmployeeID
FROM Employee
WHERE NOT EXISTS (SELECT 1
FROM LoadHistory
WHERE LoadHistory.YearWeek = '2015-26' AND
LoadHistory.EmployeeID = Employee.EmployeeID
);
如果您将LoadHistory.EmployeeId
声明为NOT NULL
,编译器可能足够聪明以避免这种情况。
我还应该提到NOT EXISTS
可以利用LoadHistory(EmployeeId)
或LoadHistory(EmployeeId, YearWeek)
上的索引。 NOT IN
版本可以使用LoadHistory(YearWeek)
或LoadHistory(YearWeek, EmployeeId)
。也许你的索引解释了性能上的差异。
答案 1 :(得分:0)
这是in
和exists
之间的差异。 Exists
在子查询第一次匹配给定条件时求值为true。另一方面,in
将扫描整个表格。