T-SQL 的 CROSS APPLY 用法與查詢正在執行的 SQL 語句

CROSS APPLY 是 SQL Server(T-SQL)中的一種運算子,用於將左側資料表的每一列,與右側的「資料表值函式 (TVF)」或「子查詢」進行運算和連接。

核心特性與運作方式

  • 等同於 INNER JOIN:當右側運算子有回傳資料時,才會保留該列;若右側回傳空值(0筆資料),左側的資料會被過濾掉(不顯示)。
  • 不使用 ON 條件:它不需要像 JOIN 一樣寫 ON 進行鍵值比對,而是直接把左側資料表的欄位「當作參數傳入」右側的子查詢或函數中。
  • 逐行運算 (Row-by-Row):它會對左側資料表的每一筆記錄,去執行一次右側的運算式。

基本語法結構

SQL
SELECT A.col1, B.col2
FROM TableA AS A
CROSS APPLY (
    -- 右側可以是資料表值函式 (TVF) 或具備關聯的子查詢
    SELECT col2 
    FROM TableB AS B 
    WHERE B.RefID = A.ID
) AS B;

取出每群組的「前 N 筆」資料(Top N per group)

SQL
SELECT C.CustomerName, O.OrderDate, O.Amount
FROM Customers AS C
CROSS APPLY (
    Top 2 OrderDate, Amount
    FROM Orders AS Ord
    WHERE Ord.CustomerID = C.CustomerID
    ORDER BY OrderDate DESC
) AS O;

字串拆解(將單行多值拆成多行資料)

SQL
SELECT P.ProductName, V.value AS Tag
FROM Products AS P
CROSS APPLY STRING_SPLIT(P.Tags, ',') AS V;

CROSS APPLY 與 OUTER APPLY 的差異

比較項目CROSS APPLYOUTER APPLY
類比的 JOIN類似 INNER JOIN類似 LEFT OUTER JOIN
右側無資料時不顯示左側該筆資料仍會顯示左側資料,右側欄位補 NULL

實例:查詢正在執行的 SQL 語句

SQL
/**
* 抓取目前正在執行的慢速查詢(含 SQL 語法與執行計畫)
* 這段腳本結合了 sys.dm_exec_requests、sys.dm_exec_sessions,並透過 sys.dm_exec_sql_text 與 sys.dm_exec_query_plan 函數,
* 直接將正在執行的 SQL 語句和圖形化執行計畫拉出來。
* 
*/
SELECT 
    r.session_id AS [SPID],
    s.login_name AS [登入帳號],
    s.host_name AS [用戶端電腦],
    DB_NAME(r.database_id) AS [資料庫名稱],
    r.status AS [狀態],
    r.command AS [命令類型],
    r.cpu_time AS [CPU 時間 (ms)],
    r.total_elapsed_time AS [總執行時間 (ms)],
    r.logical_reads AS [邏輯讀取次數],
    r.writes AS [寫入次數],
    -- 抓取目前正在執行的特定那一段 SQL 語句
    SUBSTRING(st.text, (r.statement_start_offset/2)+1,   
        ((CASE r.statement_end_offset   
          WHEN -1 THEN DATALENGTH(st.text)  
         ELSE r.statement_end_offset END - r.statement_start_offset)/2) + 1) AS [正在執行的 SQL 語法],
    st.text AS [完整批次 SQL 語法],
    qp.query_plan AS [XML 執行計畫] -- 點擊結果可直接看圖形化計畫
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) qp
WHERE s.is_user_process = 1 -- 排除 SQL Server 內部系統常駐程序
  AND r.session_id <> @@SPID -- 排除目前正在執行此查詢的自己
ORDER BY r.total_elapsed_time DESC; -- 依照執行時間由長到短排序

JOIN 的核心用途

在 SQL 中,JOIN 的核心用途是將兩個(或多個)資料表依據特定的關聯欄位(通常是主鍵與外鍵)橫向結合。

最常見的 INNER JOIN、LEFT JOIN 與 RIGHT JOIN,其主要差異在於當關聯條件不成立(找不到對應資料)時,如何處理那些「落單」的資料。

我們假設有兩個資料表:

  • 左表 (Table A):客戶資料(有客戶 A, B, C)
  • 右表 (Table B):訂單資料(有客戶 A, B 的訂單,以及一個匿名客戶 D 的訂單)
JOIN 類型核心邏輯結果說明
INNER JOIN只取交集只有 Table A 和 Table B 都有對應的資料才會顯示(只顯示 A, B 的資料)。
LEFT JOIN左表全留左表 (A) 的資料全部保留。如果右表 (B) 找不到對應,右表的欄位直接顯示 NULL(顯示 A, B, C,C 的訂單欄位為 NULL)。
RIGHT JOIN右表全留右表 (B) 的資料全部保留。如果左表 (A) 找不到對應,左表的欄位直接顯示 NULL(顯示 A, B, D,D 的客戶欄位為 NULL)。

INNER JOIN (內部連接)

最嚴格的連接方式。只有當兩張表同時滿足 ON 後面的條件時,該筆資料才會出現在結果中。

SQL
SELECT A.ID, A.Name, B.OrderNo
FROM TableA A
INNER JOIN TableB B ON A.ID = B.CustomerID;

適用情境:

  • 想要找出「有消費的客戶名單與其訂單」。
  • 想要找出「有被指派部門的員工清單」。

LEFT JOIN / LEFT OUTER JOIN (左外部連接)

以 FROM 後面的表(左表)為主。不論右表有沒有對應的資料,左表的每一行都一定會出現在結果中。

SQL
SELECT A.ID, A.Name, B.OrderNo
FROM TableA A
LEFT JOIN TableB B ON A.ID = B.CustomerID;

適用情境:

  • 想要找出「不論有沒有消費,所有的客戶名單以及他們的最新訂單」。
  • 反向篩選:結合 WHERE B.CustomerID IS NULL,可以找出「從未消費過的蚊子客戶」。

RIGHT JOIN / RIGHT OUTER JOIN (右外部連接)

以 JOIN 後面的表(右表)為主。不論左表有沒有對應的資料,右表的每一行都一定會出現在結果中。

SQL
SELECT A.ID, A.Name, B.OrderNo
FROM TableA A
RIGHT JOIN TableB B ON A.ID = B.CustomerID;

適用情境與建議:

  • 想要找出「所有的訂單,以及該訂單對應的客戶(即使該訂單沒有對應到現存客戶)」。
  • 開發習慣:在實際開發中,RIGHT JOIN 較少使用。因為任何 RIGHT JOIN 只要把資料表的順序對調,就可以改寫成更直覺、符合人類從左到右閱讀習慣的 LEFT JOIN。

總結:我該用哪一個?

  • 兩邊都要有資料 : 用 INNER JOIN
  • 左邊一定要全留(右邊沒資料就補 NULL): 用 LEFT JOIN
  • 右邊一定要全留(左邊沒資料就補 NULL):(或建議改寫為 LEFT JOIN)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *