現在是時候聊聊人生了。人生總有那一面是逃不掉的:錯誤。抓 bug、修 bug、理解 bug——這就是搞資料一定會遇到的日常。來看看在 SQL 裡用 JOIN 時會踩到哪些地雷,還有怎麼閃過去。
錯誤 1:漏掉 JOIN 條件——產生笛卡兒積
最常見的錯誤就是——忘記寫 ON 條件。這樣會產生笛卡兒積,也就是第一張表的每一列都會跟第二張表的每一列配對。結果你會拿到一大堆沒意義的資料列,超級混亂。
舉個例子。假設我們有這兩張表:
學生 (students):
| student_id | name |
|---|---|
| 1 | Otto |
| 2 | Anna |
課程 (courses):
| course_id | course_name |
|---|---|
| 101 | 數學 |
| 102 | 歷史 |
現在我們寫個查詢,忘記 ON:
SELECT *
FROM students
JOIN courses;
結果:
| student_id | name | course_id | course_name |
|---|---|---|---|
| 1 | Otto | 101 | 數學 |
| 1 | Otto | 102 | 歷史 |
| 2 | Anna | 101 | 數學 |
| 2 | Anna | 102 | 歷史 |
這看起來完全不對吧?這種災難就叫做 笛卡兒積。
怎麼修:用 ON 指定表格之間的關聯欄位。
SELECT *
FROM students
JOIN courses
ON students.student_id = courses.course_id;
然後又會遇到新一輪的錯誤...
防呆設計
這問題太常見了,所以 PostgreSQL 直接禁止你用 JOIN 但沒寫 ON 和條件。
如果你真的想每一列都配每一列,可以不用 JOIN,直接這樣寫:
SELECT *
FROM students, courses;
還有第 3 種情況——什麼時候 JOIN 不用 ON 也可以:
- 用
NATURAL JOIN—— 會自動找名字一樣的欄位來連。 - 用
USING—— 你自己指定哪些欄位要連。 CROSS JOIN—— 永遠不用條件,就是笛卡兒積。
錯誤 2:JOIN 條件寫錯
有時候你有寫 JOIN 條件,但寫錯了。比如不是用 key 連,而是用一些根本沒關係的欄位。
假設我們想查學生和他們選的課程,但不小心用錯欄位來 JOIN:
SELECT *
FROM students
JOIN courses
ON students.student_id = courses.course_id;
這樣查出來的結果一定怪怪的,因為 student_id 跟 course_id 完全是兩回事。
怎麼修:確認你用對欄位來 JOIN。正確的 JOIN 可能長這樣(假設你有一張 enrollments 表來連學生和課程):
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
錯誤 3:結果資料列重複
當你查詢裡加了好幾個 JOIN,有時候會出現重複的資料列。這通常是因為 JOIN 的表裡有重複資料,或是 JOIN 條件寫錯。
比如 Otto 這個學生在 enrollments 表裡同一門課被記錄了兩次。
enrollments 裡的資料:
| student_id | course_id |
|---|---|
| 1 | 101 |
| 1 | 101 |
現在用 JOIN 查會變這樣:
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
結果:
| name | course_name |
|---|---|
| Otto | 數學 |
| Otto | 數學 |
怎麼修:第一,確保你的表裡沒有重複資料。第二,如果這是預期的行為,要去掉重複可以用 DISTINCT:
SELECT DISTINCT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
錯誤 4:用 INNER JOIN 會遺失資料列
INNER JOIN 只會回傳兩張表都有對應資料的列。如果其中一張表沒有對應值,那一列就會被丟掉。你可能會因為 JOIN 類型選錯而遺失資料。
比如有個學生還沒選任何課:
學生 (students):
| student_id | name |
|---|---|
| 1 | Otto |
| 2 | Anna |
| 3 | Dhany |
選課紀錄 (enrollments):
| student_id | course_id |
|---|---|
| 1 | 101 |
| 2 | 102 |
現在用 INNER JOIN 查:
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
結果:
| name | course_name |
|---|---|
| Otto | 數學 |
| Anna | 歷史 |
那 Dhany 呢?如果你想把沒選課的學生也查出來,要用 LEFT JOIN:
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id;
錯誤 5:NULL 值處理不當
如果某張表有 NULL(空值),這些資料有可能被過濾掉(比如你加了 WHERE 條件)。
舉例:你用了 LEFT JOIN,但又加了 WHERE 來過濾。
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id
WHERE courses.course_name = '數學';
這樣沒選課的學生還是查不出來,雖然你用了 LEFT JOIN。
怎麼修:如果你想把沒課的學生也查出來,可以把 WHERE 換成 ON,或加一個條件:
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id
WHERE courses.course_name IS NULL OR courses.course_name = '數學';
錯誤 6:搞混 JOIN 類型
你會搞不清楚該用哪種 JOIN。比如用了 RIGHT JOIN,其實只要換一下表的順序就可以用 LEFT JOIN。
怎麼避免混亂:
- 能用
LEFT JOIN就用,這樣比較直覺。 - 換一下表的順序,就不用
RIGHT JOIN了。
GO TO FULL VERSION