子查詢將一個 SELECT 嵌套在另一個查詢中,MySQL 支持三大基本用法。掌握改寫為 JOIN 的規律,能顯著提升執行計劃效率。
內層返回單值,外層用於比較。內層必須只返回一行一列。
-- 某天測驗成績
SELECT * FROM score
WHERE event_id = (SELECT event_id FROM event WHERE date='2002-03-21' AND type='Q');
-- 最早生日的總統(錯誤寫法)
SELECT * FROM president WHERE birth = MIN(birth); -- 聚合函數不能直接用於 WHERE
-- 正確:標量子查詢
SELECT * FROM president WHERE birth = (SELECT MIN(birth) FROM president);
「由外向內」:外層每行傳入內層檢查是否存在匹配。內層 SELECT * 只關心行數,不關心具體列。
-- 兩表都存在的數據
SELECT i1 FROM t1 WHERE EXISTS (SELECT * FROM t2 WHERE t1.i1 = t2.i2);
-- t1 有、t2 無的數據
SELECT i1 FROM t1 WHERE NOT EXISTS (SELECT * FROM t2 WHERE t1.i1 = t2.i2);
內層返回單列集合,外層檢查值是否在集合中。
-- 交集
SELECT i1 FROM t1 WHERE i1 IN (SELECT i2 FROM t2);
-- 差集
SELECT i1 FROM t1 WHERE i1 NOT IN (SELECT i2 FROM t2);
-- 多值列表簡寫
SELECT * FROM student WHERE state IN ('A','B');
-- 子查詢
SELECT * FROM score WHERE event_id IN (SELECT event_id FROM event WHERE type='T');
-- 改寫
SELECT score.* FROM score, event
WHERE score.event_id = event.event_id AND event.type = 'T';
-- 通用模式
SELECT * FROM table1 WHERE col1 IN (SELECT col2a FROM table2 WHERE col2b = value);
-- ↓
SELECT table1.* FROM table1, table2
WHERE table1.col1 = table2.col2a AND table2.col2b = value;
-- 子查詢:從未缺勤的學生
SELECT * FROM student WHERE student_id NOT IN (SELECT student_id FROM absence);
-- 改寫:LEFT JOIN
SELECT student.* FROM student
LEFT JOIN absence ON student.student_id = absence.student_id
WHERE absence.student_id IS NULL;
-- 通用模式
SELECT * FROM table1 WHERE col1 NOT IN (SELECT col2 FROM table2);
-- ↓
SELECT table1.* FROM table1
LEFT JOIN table2 ON table1.col1 = table2.col2
WHERE table2.col2 IS NULL;
-- ⚠️ 前提:table2.col2 必須聲明為 NOT NULL
| 維度 | 子查詢 | JOIN 改寫 |
|---|---|---|
| 可讀性 | 語義直觀,業務邏輯清晰 | 需理解連接語義 |
| 優化器支持 | MySQL 5.6+ 顯著增強,可自動轉換 | 傳統優化器更成熟 |
| NULL 語義 | NOT IN 遇 NULL 返回空集(陷阱) | LEFT JOIN + IS NULL 語義穩定 |
| 執行計劃 | 可能物化/去相關 | 直接走 JOIN 算子 |
NOT IN 永遠返回空。改用 NOT EXISTS 或 LEFT JOIN ... IS NULLLIMIT 1