环境: SQL 2008 R2
我使用子查询创建了一个派生表,并与主表连接。我只想知道子查询是仅执行一次还是将对结果集中的每一行执行。请考虑以下示例(虚构表名仅供参考)
SELECT E.EID,DT.Salary FROM Employees E
INNER JOIN
(
SELECT EID, (SR.Rate * AD.DaysAttended) Salary
FROM SalaryRate SR
INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID
那么,用于内连接的子查询只会执行一次或多次?
如果我使用OUTER APPLY重写上面的查询,我肯定知道将为每一行执行子查询。见下文。
SELECT E.EID,DT.Salary FROM Employees E
OUTER APPLY
(
SELECT (SR.Rate * AD.DaysAttended) Salary
FROM SalaryRate SR
INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
WHERE SR.EID=E.EID
) DT --Derived Table for outer apply
所以只想确保Inner Join只执行一次子查询。
答案 0 :(得分:10)
首先要注意的是,您的查询无法比较,OUTER APPLY
需要替换为CROSS APPLY
,INNER JOIN
替换为LEFT JOIN
。
虽然它们具有可比性,但您可以看到两个查询的查询计划完全相同。我刚刚嘲笑了一个样本DDL:
CREATE TABLE #Employees (EID INT NOT NULL);
INSERT #Employees VALUES (0);
CREATE TABLE #SalaryRate (EID INT NOT NULL, Rate MONEY NOT NULL);
CREATE TABLE #AttendanceDetails (EID INT NOT NULL, DaysAttended INT NOT NULL);
运行以下内容:
SELECT E.EID,DT.Salary FROM #Employees E
OUTER APPLY
(
SELECT (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply
SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;
提供以下计划:
改为INNER / CROSS:
SELECT E.EID,DT.Salary FROM #Employees E
CROSS APPLY
(
SELECT (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply
SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;
提供以下计划:
这些是外部表中没有数据的计划,员工中只有一行,所以不太现实。在外部应用的情况下,SQL Server能够确定员工中只有一行,因此仅对外部表执行嵌套循环连接(即逐行查找)将是有益的。在员工中放置1,000行后,使用LEFT JOIN / OUTER APPLY产生以下计划:
你可以看到这里的连接现在是一个哈希匹配连接,这意味着(用它最简单的术语)SQL Server已经确定最好的计划是首先执行外部查询,哈希结果和然后从员工那里查询。然而,这并不意味着执行整个子查询并存储结果,为简单起见,您可以考虑这一点,但仍然可以使用外部查询的谓词,例如,如果子查询在内部执行和存储,以下查询会产生巨大的开销:
SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID
WHERE E.EID = 1;
检索所有员工费率,存储结果,实际查找一名员工的重点是什么?检查执行计划显示EID = 1
谓词传递给#AttendanceDetails
上的表扫描:
以下几点的答案是:
取决于。使用APPLY
如果可能,SQL Server将尝试将查询重写为JOIN,因为这将产生最佳计划,因此使用OUTER APPLY
并不能保证每行都会执行一次查询。类似地,使用LEFT JOIN
并不能保证查询只执行一次。
SQL是一种声明性语言,因为你告诉它你想要它做什么,而不是怎么做,所以你不应该依赖特定的命令来引出特定的行为,相反,如果你发现性能问题,检查执行计划和IO统计信息,了解它是如何做到的,并确定如何改进查询。
此外,SQL Server不会对子查询进行数组化,通常将定义扩展到主查询中,因此即使您已编写:
SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
FROM #SalaryRate SR
INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;
实际执行的内容更像是:
SELECT e.EID, sr.Rate * ad.DaysAttended AS Salary
FROM #Employees e
INNER JOIN #SalaryRate sr
on e.EID = sr.EID
INNER JOIN #AttendanceDetails ad
ON ad.EID = sr.EID;
答案 1 :(得分:2)
使用INNER JOIN,您的子查询将只执行一次,其记录可能会在内部存储在复杂操作的tempdb工作表中,然后与第一个表一起使用。
使用APPLY子句,将对第一个表中的每一行执行子查询。
编辑:使用CTE
;with SalaryRateCTE as
(
SELECT EID, (SR.Rate * AD.DaysAttended) AS Salary
FROM SalaryRate SR
INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
)
SELECT E.EID, DT.Salary
FROM Employees E
INNER JOIN SalaryRateCTE DT --Derived Table for inner join
ON DT.EID = E.EID
答案 2 :(得分:1)
子查询只会被评估一次。为了避免混淆,我们可以简单地将子查询视为单个表/视图,因为外部和内部内部查询不是相关的。