交叉应用和外部应用基础知识

当表值函数在右表达式中时,将使用 Apply。

创建一个 Department 表来保存有关部门的信息。然后创建一个 Employee 表,其中包含有关员工的信息。请注意,每个员工都属于一个部门,因此 Employee 表与 Department 表具有参照完整性。

第一个查询从 Department 表中选择数据,并使用 CROSS APPLY 来评估 Department 表的每个记录的 Employee 表。第二个查询只是将 Department 表与 Employee 表连接起来,并生成所有匹配的记录。

SELECT *
FROM Department D
CROSS APPLY (
    SELECT *
    FROM Employee E
    WHERE E.DepartmentID = D.DepartmentID
) A
GO
SELECT *
FROM Department D
INNER JOIN Employee E
  ON D.DepartmentID = E.DepartmentID

如果你看一下它们产生的结果,它就是完全相同的结果集; 它与 JOIN 有何不同,它如何帮助编写更有效的查询。

脚本#2 中的第一个查询从 Department 表中选择数据,并使用 OUTER APPLY 来评估 Department 表的每个记录的 Employee 表。对于那些在 Employee 表中没有匹配的行,这些行包含 NULL 值,如第 5 行和第 6 行所示。第二个查询只使用 Department 表和 Employee 表之间的 LEFT OUTER JOIN。正如预期的那样,查询返回 Department 表中的所有行; 甚至是那些在 Employee 表中没有匹配的行。

SELECT *
FROM Department D
OUTER APPLY (
    SELECT *
    FROM Employee E
    WHERE E.DepartmentID = D.DepartmentID
) A
GO
SELECT *
FROM Department D
LEFT OUTER JOIN Employee E
  ON D.DepartmentID = E.DepartmentID
GO

即使上述两个查询返回相同的信息,执行计划也会略有不同。但是,成本方面的差别不大。

现在是时候看看真正需要 APPLY 运算符的位置了。在脚本#3 中,我创建了一个表值函数,它接受 DepartmentID 作为参数,并返回属于该部门的所有员工。下一个查询从 Department 表中选择数据,并使用 CROSS APPLY 与我们创建的函数连接。它从外表表达式(在我们的案例中为 Department 表)中传递每一行的 DepartmentID,并为与相关子查询类似的每一行计算函数。下一个查询使用 OUTER APPLY 代替 CROSS APPLY,因此与仅返回相关数据的 CROSS APPLY 不同,OUTER APPLY 也返回非相关数据,将 NULL 放入缺失的列中。

CREATE FUNCTION dbo.fn_GetAllEmployeeOfADepartment (@DeptID AS int)
RETURNS TABLE
AS
  RETURN
  (
  SELECT
    *
  FROM Employee E
  WHERE E.DepartmentID = @DeptID
  )
GO
SELECT
  *
FROM Department D
CROSS APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID)
GO
SELECT
  *
FROM Department D
OUTER APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID)
GO

那么现在如果你想知道,我们可以使用简单的连接代替上述查询吗?然后答案为 NO,如果用 INNER JOIN / LEFT OUTER JOIN 替换上述查询中的 CROSS / OUTER APPLY,指定 ON 子句(某事为 1 = 1)并运行查询,你将得到多部分标识符 D.DepartmentID无法受约束。错误。这是因为使用 JOIN 时,外部查询的执行上下文与函数(或派生表)的执行上下文不同,并且你不能将外部查询中的值/变量作为参数绑定到函数。因此,此类查询需要 APPLY 运算符。