子查詢是 SQL 表達複雜邏輯的核心工具。本文按結果集角色劃分為三類,給出典型場景、寫法範例、優化建議與常見陷阱。
特徵:內層結果作為外層 WHERE 條件的比較值。
-- 查詢最新商品(無 ORDER BY) SELECT goods_id, goods_name FROM goods WHERE goods_id = (SELECT MAX(goods_id) FROM goods);
-- 每個分類最新商品 SELECT cat_id, goods_id, goods_name FROM goods WHERE goods_id IN (SELECT MAX(goods_id) FROM goods GROUP BY cat_id);
IN 子查詢 MySQL 5.6+ 優化為 SEMI JOIN,效率接近 JOIN。NOT IN 遇 NULL 導致空結果集陷阱 → 改用 NOT EXISTS 或 LEFT JOIN ... IS NULL。JOIN 或 APPLY(MariaDB/8.0 LATERAL)。特徵:內層結果作為外層 FROM 的數據源,必須起別名。
-- 找出掛科 ≥2 門學生的平均分
SELECT name, AVG(score) AS avg_score
FROM stu
WHERE name IN (
SELECT name FROM (
SELECT name, COUNT(*) AS fail_cnt
FROM stu
WHERE score < 60
GROUP BY name
HAVING fail_cnt >= 2
) AS t
)
GROUP BY name;
ref 訪問。CTE (WITH) 替代嵌套 FROM 子查詢,可讀性強且 8.0+ 支援物化/合併選擇。特徵:外層行驅動內層執行,內層只判斷是否存在(返回 TRUE/FALSE),不返回具體數據。
SELECT cat_id, cat_name FROM category WHERE EXISTS ( SELECT 1 FROM goods WHERE goods.cat_id = category.cat_id );
| 場景 | 推薦 | 理由 |
|---|---|---|
| 外層大、內層小(有索引) | EXISTS | 外層驅動,內層索引查找極快 |
| 外層小、內層大 | IN | 內層物化/半連接,避免外層大量驅動 |
| 需返回內層列 | JOIN | EXISTS 無法投影內層列 |
| NOT IN 含 NULL 風險 | NOT EXISTS | 語義正確、索引友好 |
WHERE EXISTS (SELECT 1 FROM goods WHERE cat_id = c.cat_id LIMIT 1)
明確語義:只要存在一行即可,優化器可提前終止掃描。
JOIN 替代子查詢(可讀性允許時),利用優化器 Join Reorder。NOT IN 禁用,全面改用 NOT EXISTS / LEFT JOIN ... IS NULL。/*+ MAX_EXECUTION_TIME(1000) */ 防止物化超時(8.0+)。EXPLAIN 觀察 select_type(DEPENDENT SUBQUERY/DERIVED/MATERIALIZED)與 type 確認訪問策略。