LEFT / RIGHT / FULL JOIN
為什麼需要 OUTER JOIN?
INNER JOIN 只回傳兩邊都有配對的資料。 但如果你想看到所有學生(包含沒選課的),就需要 OUTER JOIN。
LEFT JOIN
LEFT JOIN = 左邊的表全部保留,右邊沒配對的填 NULL。
SELECT
s.Name AS 學生,
c.Name AS 課程,
e.Score AS 分數
FROM Students s -- ← 左表:全部保留
LEFT JOIN Enrollments e -- ← 右表:有配對的才連
ON s.Id = e.StudentId
LEFT JOIN Courses c
ON e.CourseId = c.Id;
結果:
學生 | 課程 | 分數
------+------+------
小明 | 數學 | 85
小明 | 英文 | 92
小華 | 數學 | 78
小美 | NULL | NULL ← 小美沒有選課,但還是出現了!
Students Enrollments
┌────┐ ┌────┐
│ 小明│─────────▶│ 記錄│ ✅ 有配對
│ 小華│─────────▶│ 記錄│ ✅ 有配對
│ 小美│──── ✗ │ │ ⚠️ 沒配對,但左表保留,右邊填 NULL
└────┘ └────┘
用 LEFT JOIN 找出「沒有」的資料
-- 找出沒有選任何課的學生
SELECT s.Name
FROM Students s
LEFT JOIN Enrollments e ON s.Id = e.StudentId
WHERE e.Id IS NULL; -- ← 右表是 NULL = 沒有配對 = 沒選課
逐行解析:
LEFT JOIN Enrollments e -- 左連接,保留所有學生
ON s.Id = e.StudentId -- 嘗試配對
WHERE e.Id IS NULL -- 配對失敗的(e.Id 是 NULL)
-- = 在 Enrollments 裡找不到這個學生
-- = 這個學生沒有選課
💡 這是 LEFT JOIN 最常見的用法之一:找出「缺少關聯」的資料。
RIGHT JOIN
RIGHT JOIN = 右邊的表全部保留,左邊沒配對的填 NULL。
-- 查詢所有課程(包含沒人選的)
SELECT s.Name AS 學生, c.Name AS 課程
FROM Students s
RIGHT JOIN Enrollments e ON s.Id = e.StudentId
RIGHT JOIN Courses c ON e.CourseId = c.Id;
學生 | 課程
------+------
小明 | 數學
小華 | 數學
小明 | 英文
NULL | 物理 ← 物理沒人選,但還是出現了
💡 實務上 RIGHT JOIN 很少用。你可以把表的順序反過來,用 LEFT JOIN 代替:
-- 這兩個完全等價: A RIGHT JOIN B ON ... = B LEFT JOIN A ON ...
FULL OUTER JOIN
FULL JOIN = 兩邊都全部保留,沒配對的填 NULL。
SELECT s.Name AS 學生, c.Name AS 課程
FROM Students s
FULL OUTER JOIN Enrollments e ON s.Id = e.StudentId
FULL OUTER JOIN Courses c ON e.CourseId = c.Id;
學生 | 課程
------+------
小明 | 數學
小明 | 英文
小華 | 數學
小美 | NULL ← 小美沒選課
NULL | 物理 ← 物理沒人選
JOIN 比較總覽
INNER JOIN LEFT JOIN RIGHT JOIN FULL JOIN
┌──┬──┐ ┌──┬──┐ ┌──┬──┐ ┌──┬──┐
│██│██│ │██│██│ │██│██│ │██│██│
│ │██│ ← 交集 │██│██│ ← 左全部 │ │██│ ← 右全部 │██│██│ ← 全部
│██│ │ │██│ │ │██│██│ │██│ │
└──┴──┘ └──┴──┘ └──┴──┘ └──┴──┘
| JOIN 類型 | 左表沒配對 | 右表沒配對 |
|---|---|---|
| INNER | ❌ 不顯示 | ❌ 不顯示 |
| LEFT | ✅ 顯示(右填 NULL) | ❌ 不顯示 |
| RIGHT | ❌ 不顯示 | ✅ 顯示(左填 NULL) |
| FULL | ✅ 顯示 | ✅ 顯示 |
實用範例
-- 每個客戶的訂單數(包含沒下過單的客戶)
SELECT
c.Name AS 客戶,
COUNT(o.Id) AS 訂單數 -- ← 用 COUNT(o.Id) 而不是 COUNT(*)
FROM Customers c -- COUNT(o.Id) 遇到 NULL 不計算
LEFT JOIN Orders o ON c.Id = o.CustomerId
GROUP BY c.Name
ORDER BY 訂單數 DESC;
-- 找出沒有訂單的客戶
SELECT c.Name
FROM Customers c
LEFT JOIN Orders o ON c.Id = o.CustomerId
WHERE o.Id IS NULL;