你應該已經知道怎麼用 JOIN 來合併資料表了。但現實世界裡,只靠 key 對上還不夠。常常你會遇到這種需求:合併資料時,還要符合額外條件,像是只要活躍的紀錄、只要今年的資料、或只要已完成的訂單。
這時候就要用 ON 加上 AND 來擴充條件啦。
在 JOIN ... ON 裡加上額外條件,可以讓你精準控制哪些 row 會被 join,而且是在 SQL 開始產生結果之前就先過濾。這樣寫查詢:
- 更快(經過
JOIN的 row 變少了) - 更精準(過濾直接在 join 階段做)
- 用
LEFT JOIN時更可預期(跟WHERE過濾不一樣)
範例:只要有報名中的課程
假設 enrollments 這個表有學生的參與狀態:active、dropped、pending。
students 表:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
來看 enrollments 表:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
courses 表:
| id | name |
|---|---|
| 101 | Mathematics |
| 102 | Physics |
| 103 | Computer Science |
現在我們想查出只有那些有報名中課程的學生:
SELECT
students.name AS student_name,
courses.name AS course_name
FROM students
INNER JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status = 'active'
INNER JOIN courses
ON enrollments.course_id = courses.id;
查詢結果:
| student_name | course_name |
|---|---|
| Otto Song | Mathematics |
| Otto Song | Computer Science |
| Alex Lin | Mathematics |
這裡我們把 AND enrollments.status = 'active' 寫在 ON 裡面,這樣 join 的時候只會連到 active 的紀錄,而不是合併完再過濾。
為什麼不用 WHERE?
你也可以這樣寫:
...
WHERE enrollments.status = 'active'
但這樣 在 LEFT JOIN 時行為會不一樣。WHERE 過濾會把沒對到的 row(NULL)都砍掉,這樣 LEFT JOIN 就變成 INNER JOIN 了。
而 AND enrollments.status = 'active' 放在 ON 裡,會直接限制 join 的 row —— 決定哪些 row 會進 join,不是合併完才過濾。
這種寫法很重要,尤其你想保留某個表的 row,即使另一個表沒對到值(報表、分析時很常見)。
更多 ON ... AND ... 用法範例
students 表:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
enrollments 表:
| student_id | course_id | status | enrolled_at |
|---|---|---|---|
| 1 | 101 | active | 2025-02-01 |
| 1 | 103 | active | 2025-03-05 |
| 2 | 102 | dropped | 2024-05-15 |
| 3 | 101 | active | 2025-03-12 |
courses 表:
| id | name |
|---|---|
| 101 | Math |
| 102 | Physics |
| 103 | CS |
範例:只要今年的課程
SELECT
students.name,
courses.name,
enrollments.enrolled_at
FROM students
JOIN enrollments
ON students.id = enrollments.student_id
AND EXTRACT(YEAR FROM enrollments.enrolled_at) = EXTRACT(YEAR FROM CURRENT_DATE)
JOIN courses
ON enrollments.course_id = courses.id;
這裡我們只 join 跟今年有關的紀錄。
| name | name | enrolled_at |
|---|---|---|
| Otto Song | Math | 2025-02-01 |
| Otto Song | CS | 2025-03-05 |
| Alex Lin | Math | 2025-03-12 |
範例:排除某個值
JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status != 'dropped'
我們在 join 階段 就把退選的學生排除了,不是合併完才過濾。
| name | name |
|---|---|
| Otto Song | Math |
| Otto Song | CS |
| Alex Lin | Math |
條件寫在 ON 裡,PostgreSQL 可以優化 join 計劃,處理更少 row。這在資料量很大時超重要。內部過濾比 join 後再砍掉還有效率。
JOIN ON 不只是 key 對 key
很多人以為 ON 只能寫 id = id。其實你可以放:
- 邏輯運算:
AND、OR、NOT - 比較:
>、<、<>、BETWEEN、IN - 運算式:
EXTRACT、DATE_TRUNC、COALESCE、NULLIF
全部混在一起用
students 表:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
faculties 表:
| id | name | |
|---|---|---|
| 10 | Engineering | |
| 20 | Natural Sciences | |
| 30 | ← 沒有名字 (NULL) |
courses 表:
| id | name | teacher | faculty_id |
|---|---|---|---|
| 101 | Math | Liam Park | 10 |
| 102 | Physics | Chloe Zhang | 20 |
| 103 | CS | Noah Kim | 10 |
| 104 | PE | Ava Chen | 30 |
enrollments 表:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
| 3 | 104 | active |
SELECT
s.name AS student_name,
c.name AS course_name,
f.name AS faculty_name
FROM students s
JOIN enrollments e
ON s.id = e.student_id
AND e.status = 'active'
JOIN courses c
ON e.course_id = c.id
AND c.name != 'Физкультура'
JOIN faculties f
ON c.faculty_id = f.id
AND f.name IS NOT NULL;
這裡我們同時過濾:
- active 的紀錄,
- 課程不是「Физкультура」的,
- faculty 有名字的。
查詢結果:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Math | Engineering |
| Otto Song | CS | Engineering |
| Alex Lin | Math | Engineering |
希望你喜歡這堂課啦!你以後查詢時會超常用多個 JOIN 加過濾條件,幾乎每次都會用到 :)
GO TO FULL VERSION