FROM 子查詢(衍生表)與 EXISTS/NOT EXISTS 是 SQL 進階查詢的兩大核心工具。前者將子查詢結果作為臨時表參與連接,後者以「存在性判斷」替代資料比對,常能大幅簡化邏輯並提升效能。本文詳細解析語法、執行機制、優化技巧與實戰案例。
SELECT ... FROM (subquery) AS alias ...
LATERAL 突破)。-- 每位作者的平均文章長度,再篩選平均長度 > 1000 的作者
SELECT author_id, AVG(article_len) AS avg_len
FROM (
SELECT uid AS author_id, CHAR_LENGTH(content) AS article_len
FROM article
) AS t
GROUP BY author_id
HAVING avg_len > 1000;
WITH cte AS (...) SELECT ... (MySQL 8.0 CTE),優化器更易合併。WHERE 條件寫在子查詢內而非外層。/*+ MAX_EXECUTION_TIME(1000) */ Hint 限制執行時間。SELECT * FROM outer_table o
WHERE EXISTS (
SELECT 1 FROM inner_table i WHERE o.id = i.outer_id
);
**執行邏輯**:對外層每行,執行子查詢;**只要找到第一行匹配即返回 TRUE 停止掃描** (Short-circuit)。
SELECT 1、SELECT *、SELECT id 效能相同,優化器忽略選擇清單。
| 場景 | 推薦 | 原因 |
|---|---|---|
| 子查詢結果集**小**、外層**大** | IN | 子查詢物化成臨時表,外層索引查找 |
| 子查詢結果集**大**、外層**小** | EXISTS | 外層驅動,子查詢索引查找並短路 |
子查詢含 NULL | EXISTS | IN 遇 NULL 判斷為 UNKNOWN,導致不匹配 |
| 需檢查「不存在」 | NOT EXISTS | NOT IN 同樣受 NULL 影響 |
-- NOT EXISTS (推薦)
SELECT u.* FROM user u
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.uid = u.uid
);
-- LEFT JOIN IS NULL (等效,舊版本常用)
SELECT u.* FROM user u
LEFT JOIN orders o ON u.uid = o.uid
WHERE o.uid IS NULL;
-- 找出至少有一篇文章瀏覽量 > 10000 的作者
SELECT u.* FROM user u
WHERE EXISTS (
SELECT 1 FROM article a
WHERE a.uid = u.uid AND a.views > 10000
);
MySQL 5.6.5+ 引入 **Semi-Join** 優化策略,自動將符合條件的 IN/EXISTS 子查詢轉換為連接,避免物化臨時表。
optimizer_switch 控制:semijoin=on (預設開啟)。
適用子查詢特徵:
- 無聚合、無 DISTINCT、無 LIMIT
- 不包含 UNION
- 外層查詢無 GROUP BY/HAVING
# 測試資料:user 10萬行,orders 50萬行,uid 有索引 # 查詢:找出有訂單的用戶 # 方案 A: IN SELECT * FROM user WHERE uid IN (SELECT uid FROM orders); # 方案 B: EXISTS SELECT * FROM user u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.uid = u.uid); # 方案 C: INNER JOIN (去重) SELECT DISTINCT u.* FROM user u INNER JOIN orders o ON u.uid = o.uid; # 方案 D: JOIN + GROUP BY SELECT u.* FROM user u INNER JOIN orders o ON u.uid = o.uid GROUP BY u.uid;
典型結果 (MySQL 8.0):
方案 B (EXISTS) ≈ 方案 C (JOIN) < 方案 A (IN) < 方案 D (GROUP BY)。
**建議**:優先用 EXISTS 或 JOIN,配合 EXPLAIN 確認 type=ref、Extra=Using index。
| 陷阱 | 症狀 | 修正 |
|---|---|---|
NOT IN 子查詢含 NULL | 回傳 0 行 | 改用 NOT EXISTS 或 LEFT JOIN ... IS NULL |
| 衍生表物化導致磁碟 I/O 飆升 | EXPLAIN 顯示 derived、Using temporary | 改用 CTE、下推條件、或增加索引滿足 Derived Merge |
EXISTS 子查詢無索引 | 每行外層全掃內表 | 在內表關聯欄位建索引 |
結語: FROM 子查詢適合「先聚合再關聯」的二次運算;EXISTS/NOT EXISTS 適合「存在性檢查」,天然短路特性配合索引極為高效。MySQL 8.0 CTE 與 Derived Merge 讓衍生表不再是效能殺手。實戰中請以 EXPLAIN 為準,選擇執行計畫最優的寫法。