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

lov*_*mar 11 sql-server join subquery sql-server-2008

环境: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
Run Code Online (Sandbox Code Playgroud)

那么,用于Inner Join的子查询只会被执行一次或多次?

如果我使用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
Run Code Online (Sandbox Code Playgroud)

所以只想确保Inner Join只执行一次子查询.

Gar*_*thD 13

首先要注意的是,您的查询无法比较,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);
Run Code Online (Sandbox Code Playgroud)

运行以下内容:

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;
Run Code Online (Sandbox Code Playgroud)

给出以下计划:

在此输入图像描述

并改为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;
Run Code Online (Sandbox Code Playgroud)

给出以下计划:

在此输入图像描述

这些是外部表中没有数据的计划,员工中只有一行,因此不太现实.在外部应用的情况下,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;
Run Code Online (Sandbox Code Playgroud)

检索所有员工费率,存储结果,实际查找一名员工的重点是什么?检查执行计划显示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;
Run Code Online (Sandbox Code Playgroud)

实际执行的更像是:

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;
Run Code Online (Sandbox Code Playgroud)