| 模式 | 關鍵字 | 適用場景 | 效能特性 |
|---|---|---|---|
| 明確 JOIN | `INNER/LEFT/RIGHT/FULL JOIN ... ON` | 關聯查詢、星型結構、維度建模 | 優化器成熟、可控制聯結順序/類型 |
| 隱含 JOIN (舊語法) | `FROM A, B WHERE A.id = B.id` | 舊系統相容 | **不推薦**:語義模糊、易產生笛卡兒積 |
| 集合運算 | `UNION/EXCEPT/INTERSECT` | 合併同結構結果、差集/交集 | 隱式 `DISTINCT`,大資料量慎用 |
-- 內聯結:雙方都有匹配
SELECT o.OrderID, c.CustomerName
FROM Orders o
INNER JOIN Customers c ON o.CustomerID = c.CustomerID;
-- 左外部聯結:保留左表所有列
SELECT c.CustomerName, o.OrderID
FROM Customers c
LEFT JOIN Orders o ON c.CustomerID = o.CustomerID;
-- 右外部聯結:保留右表所有列 (可改 LEFT 交換順序)
SELECT o.OrderID, c.CustomerName
FROM Orders o
RIGHT JOIN Customers c ON o.CustomerID = c.CustomerID;
-- 全外部聯結:保留雙方所有列
SELECT c.CustomerName, o.OrderID
FROM Customers c
FULL JOIN Orders o ON c.CustomerID = o.CustomerID;
-- 交叉聯結:笛卡兒積 (無 ON 條件)
SELECT c.CustomerName, p.ProductName
FROM Customers c
CROSS JOIN Products p; -- 小心資料量爆炸
`CROSS APPLY` / `OUTER APPLY` 允許右側表達式引用左側資料列欄位:
-- 取得每個客戶最近 3 筆訂單
SELECT c.CustomerName, o.OrderID, o.OrderDate
FROM Customers c
CROSS APPLY (
SELECT TOP 3 OrderID, OrderDate
FROM Orders
WHERE CustomerID = c.CustomerID
ORDER BY OrderDate DESC
) o;
-- OUTER APPLY:右側無結果仍保留左側列 (NULL 填充)
SELECT c.CustomerName, o.OrderID
FROM Customers c
OUTER APPLY (
SELECT TOP 1 OrderID
FROM Orders
WHERE CustomerID = c.CustomerID
ORDER BY OrderDate DESC
) o;
適用場景:逐列計算、取 Top N、拆分分隔字串、調用表值函數。
| 運算子 | 語義 | 隱式行為 | 保留重複 |
|---|---|---|---|
| `UNION` | 並集 | `DISTINCT` | `UNION ALL` |
| `EXCEPT` | 差集 (左-右) | `DISTINCT` | 無 ALL 變體 |
| `INTERSECT` | 交集 | `DISTINCT` | 無 ALL 變體 |
-- 所有客戶與員工姓名合併 (去重)
SELECT FirstName, LastName FROM Customers
UNION
SELECT FirstName, LastName FROM Employees;
-- 僅出現在客戶不在員工的姓名
SELECT FirstName, LastName FROM Customers
EXCEPT
SELECT FirstName, LastName FROM Employees;
-- 兩者皆有的姓名
SELECT FirstName, LastName FROM Customers
INTERSECT
SELECT FirstName, LastName FROM Employees;
規則:欄位數、順序、相容型別必須一致;`ORDER BY` 僅能放最後。
-- 找出有訂單的客戶
SELECT * FROM Customers
WHERE CustomerID IN (SELECT DISTINCT CustomerID FROM Orders);
-- 等效 JOIN (通常更快)
SELECT DISTINCT c.* FROM Customers c JOIN Orders o ON c.CustomerID = o.CustomerID;
-- 找出每個客戶最新訂單日期
SELECT c.CustomerName,
(SELECT MAX(OrderDate) FROM Orders o WHERE o.CustomerID = c.CustomerID) AS LatestOrder
FROM Customers c;
-- EXISTS 版本 (半聯結,通常最優)
SELECT c.CustomerName
FROM Customers c
WHERE EXISTS (SELECT 1 FROM Orders o WHERE o.CustomerID = c.CustomerID);
| 場景 | 推薦寫法 | 理由 |
|---|---|---|
| 單純存在性檢查 | `EXISTS` / `IN` (小結果集) | 半聯結,找到即停止,不產生重複列 |
| 需從子查詢回傳欄位 | `JOIN` / `APPLY` | 避免重複掃描,優化器可重排序 |
| 標量子查詢 SELECT 列表 | 改 `OUTER APPLY` 或 `LEFT JOIN` | 標量子查詢每列執行,極慢 |
| 大結果集 `IN` 子查詢 | 改 `EXISTS` 或 `JOIN` | `IN` 可能產生大量比較/排序 |
-- 非遞歸:簡化複雜查詢
WITH TopCustomers AS (
SELECT CustomerID, SUM(TotalAmt) AS TotalSales
FROM Orders
WHERE OrderDate >= DATEADD(YEAR, -1, GETDATE())
GROUP BY CustomerID
HAVING SUM(TotalAmt) > 100000
)
SELECT c.CustomerName, tc.TotalSales
FROM Customers c JOIN TopCustomers tc ON c.CustomerID = tc.CustomerID;
-- 遞歸:組織架構/物料清單
WITH OrgChart AS (
SELECT EmployeeID, ManagerID, EmployeeName, 0 AS Level
FROM Employees WHERE ManagerID IS NULL
UNION ALL
SELECT e.EmployeeID, e.ManagerID, e.EmployeeName, oc.Level + 1
FROM Employees e JOIN OrgChart oc ON e.ManagerID = oc.EmployeeID
)
SELECT * FROM OrgChart ORDER BY Level, EmployeeName;
-- 預設最大遞歸 100 層,可用 OPTION (MAXRECURSION 0) 移除限制
| 錯誤 | 修正 |
|---|---|
| `LEFT JOIN` 後 `WHERE RightCol = 'val'` 變成內聯結 | 條件移至 `ON` 子句:`LEFT JOIN ... ON ... AND RightCol = 'val'` |
| `UNION` 大量資料極慢 | 改 `UNION ALL` + 應用層去重,或建立暫存表索引後查詢 |
| 相關子查詢在 SELECT 列表導致效能災難 | 改 `OUTER APPLY` 或 `LEFT JOIN` 衍生表 |
| `CROSS JOIN` 無意產生億級笛卡兒積 | 檢查是否遺漏 `ON` 條件,加 `TOP` 限制測試 |
| 隱含 JOIN 產生錯誤結果 | 全面改用明確 `JOIN ... ON` 語法 |
下一篇將講解子查詢合併查詢進階:`IN`/`NOT IN`/`EXISTS`/`NOT EXISTS` 邏輯差異、彙總函數在子查詢中的應用、窗口函數 (ROW_NUMBER/RANK/DENSE_RANK) 替代子查詢模式。