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 APPLY | OUTER 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)




