CodeGym /課程 /SQL SELF /使用子查詢時常見的錯誤

使用子查詢時常見的錯誤

SQL SELF
等級 14, 課堂 4
開放

用子查詢就像在玩脫衣西洋棋:一開始看起來都很簡單,直到你下一步出錯。那錯誤從哪來?通常是因為沒搞懂語法、忽略 SQL 的邏輯特性,或只是沒仔細看。在這堂課我們會聊聊最常見的錯誤,還有怎麼避開它們。

語法錯誤

子查詢對語法很挑剔。漏掉逗號、括號或 alias 都可能讓你的查詢直接掛掉。我們來看幾個常見的問題。

漏掉括號

括號在用子查詢時超級重要。子查詢一定要包在小括號裡,少一對都會語法錯誤。

錯誤範例:

SELECT student_name
FROM students
WHERE student_id IN SELECT student_id FROM enrollments);

錯誤:

ERROR:  syntax error at or near "SELECT"

修正:

SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments);

說明:IN 裡的查詢一定要包在括號裡,這樣 SQL 才知道你要執行子查詢。

沒有 alias

當你在 FROM 裡用子查詢時,記得一定要給它 alias。不然 PostgreSQL 會直接搞混。

錯誤範例:

SELECT student_name, avg_score
FROM (SELECT student_id, AVG(score) AS avg_score FROM grades GROUP BY student_id)
WHERE avg_score > 80;

錯誤:

ERROR:  subquery in FROM must have an alias

修正:

SELECT student_name, avg_score
FROM (SELECT student_id, AVG(score) AS avg_score FROM grades GROUP BY student_id) AS subquery
WHERE avg_score > 80;

說明:PostgreSQL 要求每個暫時的表(FROM 裡的子查詢結果)都要有名字。

效能問題

子查詢,尤其沒優化過的,會讓你的資料庫跑得像蝸牛。效能通常是因為重複計算或沒加 index。

重複計算

SELECT 裡的子查詢會對每一行都算一次,超級浪費時間。

範例:

SELECT student_name,
       (SELECT COUNT(*) FROM enrollments WHERE enrollments.student_id = students.student_id) AS course_count
FROM students;

如果 students 表有幾萬行,這個子查詢每一行都會重算一次。

優化:

WITH course_counts AS (
    SELECT student_id, COUNT(*) AS course_count
    FROM enrollments
    GROUP BY student_id
)
SELECT s.student_name, c.course_count
FROM students s
LEFT JOIN course_counts c ON s.student_id = c.student_id;

用 CTE(Common Table Expression)或 join 可以避免每行都重算。這個技巧我們之後還會再講 :P

沒加 index

如果你在 WHERE 裡用複雜的子查詢,記得要把需要的欄位加上 index。

範例:

SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments WHERE course_id = 10);

如果 enrollments 表的 student_id 欄位沒 index,子查詢會全表掃描,超慢。

優化: 建立 index:

CREATE INDEX idx_enrollments_course_id ON enrollments (course_id);

你應該已經常常聽到 index 這個詞了,應該也開始好奇它到底是什麼。不過再等等,之後會詳細講。Index 很重要,但它主要是讓查詢更快,不是讓爛查詢變好查詢。它們會讓你在 production 上查詢快很多,尤其是資料表有幾百萬行的時候。

邏輯錯誤

子查詢的邏輯錯誤跟語法錯誤一樣常見。問題通常是沒搞懂 NULL、過濾條件或聚合函數的用法。

NULL 用錯

NULL 是新手的陷阱。在子查詢裡用 IN 或 NOT IN,如果有 NULL,結果會被影響。

錯誤範例:

SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments);

如果 enrollments 表裡有 student_id = NULL 的行,這個查詢會回傳空結果。因為 NOT IN 條件會變成 NULL IS NOT IN。

修正:

SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments WHERE student_id IS NOT NULL);

只要用 NOT IN,就一定要過濾掉 NULL。

過濾條件寫錯

子查詢常常用來做複雜過濾,但條件寫錯,結果就完全不是你想要的。

錯誤範例:

SELECT student_name
FROM students
WHERE (SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id) > 80;

如果有學生沒有成績,子查詢會回傳 NULL,這個學生就不會出現在結果裡。

修正:

SELECT student_name
FROM students
WHERE COALESCE((SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id), 0) > 80;

用 COALESCE 把 NULL 換成預設值。

避免錯誤的建議

想要避免用子查詢時踩雷,記得這幾點:

括號和 alias 要用對。 有問題時先檢查括號有沒有關好,子查詢有沒有 alias。

查詢要優化。 能用 JOIN、WITH 或 index 的地方就不要硬用子查詢。

注意 NULL。 子查詢裡可能會有 NULL,記得用 IS NOT NULL、COALESCE 或類似的寫法。

多測試。 每個子查詢都要單獨測試,確定它回傳你要的結果。

可讀性。 用縮排和 alias 讓你的 code 好讀。記住:一個月後你自己也可能看不懂你寫了什麼。

2
任務
SQL SELF, 等級 14, 課堂 4
上鎖
正確使用括號和別名
正確使用括號和別名
2
任務
SQL SELF, 等級 14, 課堂 4
上鎖
考慮子查詢中的 NULL
考慮子查詢中的 NULL
1
問卷/小測驗
子查詢的使用,等級 14,課堂 4
未開放
子查詢的使用
子查詢的使用
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION