交叉應用和外部應用基礎知識

當表值函式在右表示式中時,將使用 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 運算子。