内部使用子查询连接派生表

时间:2014-10-21 12:16:25

标签: sql-server sql-server-2008 join subquery

环境: 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只执行一次子查询。

3 个答案:

答案 0 :(得分:10)

首先要注意的是,您的查询无法比较,OUTER APPLY需要替换为CROSS APPLYINNER 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;

提供以下计划:

enter image description here

改为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;

提供以下计划:

enter image description here

这些是外部表中没有数据的计划,员工中只有一行,所以不太现实。在外部应用的情况下,SQL Server能够确定员工中只有一行,因此仅对外部表执行嵌套循环连接(即逐行查找)将是有益的。在员工中放置1,000行后,使用LEFT JOIN / OUTER APPLY产生以下计划:

enter image description here

你可以看到这里的连接现在是一个哈希匹配连接,这意味着(用它最简单的术语)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上的表扫描:

enter image description here

以下几点的答案是:

  • 如果我使用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;

答案 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)

子查询只会被评估一次。为了避免混淆,我们可以简单地将子查询视为单个表/视图,因为外部和内部内部查询不是相关的。