首先要注意的是你的查询没有可比性,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 上的表扫描:
所以下面几点的答案是:
- 如果我使用 OUTER APPLY 重写上述查询,我肯定会为每一行执行子查询。
- Inner Join 只会执行一次子查询。
视情况而定。如果可能,使用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;