巢狀子查詢 (子查詢中再含子查詢) 是處理多層邏輯的強大工具,但也易寫出低效或錯誤的 SQL。本文系統梳理類型、運算子、常見陷阱與優化重寫。
ORDER BY (除非配合 TOP/LIMIT 做 Top-N)。-- 找出與員工 141 同職稱的員工
SELECT last_name, job_id
FROM employees
WHERE job_id = (SELECT job_id FROM employees WHERE employee_id = 141);
-- 找出薪資等於全公司最低薪的員工
SELECT last_name, salary
FROM employees
WHERE salary = (SELECT MIN(salary) FROM employees);
陷阱:若子查詢回傳多行 → ORA-01427 / ERROR 1242。
修正:改用 IN 或 = ANY。
| 運算子 | 語義 | 適用場景 |
|---|---|---|
| IN / NOT IN | 集合成員 / 非成員 | 離散值清單比對 |
| ANY / SOME | 滿足任一值 | > ANY (1,2,3) 等同 > 1 |
| ALL | 滿足所有值 | > ALL (1,2,3) 等同 > 3 |
| EXISTS / NOT EXISTS | 存在 / 不存在 | 相關子查詢、反連接,效能最穩 |
-- 成對比較:(manager_id, department_id) 必須同時匹配同一組
SELECT employee_id, manager_id, department_id
FROM employees
WHERE (manager_id, department_id) IN (
SELECT manager_id, department_id FROM employees WHERE employee_id IN (178,174)
) AND employee_id NOT IN (178,174);
-- 結果:僅 176 符合 (149, 80) 這組組合
-- 非成對比較:分別獨立匹配
SELECT employee_id, manager_id, department_id
FROM employees
WHERE manager_id IN (SELECT manager_id FROM employees WHERE employee_id IN (174,141))
AND department_id IN (SELECT department_id FROM employees WHERE employee_id IN (174,141))
AND employee_id NOT IN (174,141);
-- 結果:多筆,只要 manager_id 在集合中且 department_id 也在集合中即可
關鍵差異:成對比較要求**同一行**滿足所有欄位組合;非成對允許來自不同行。
-- 找出薪資高於所在部門平均薪資的員工
SELECT a.last_name, a.salary, a.department_id
FROM employees a
WHERE a.salary > (
SELECT AVG(salary) FROM employees b WHERE b.department_id = a.department_id
);
外部查詢每行都會執行一次內部查詢,大數據量下效能壓力大。
-- Inline View (FROM 子查詢)
SELECT a.last_name, a.salary, a.department_id, b.avg_sal
FROM employees a
JOIN (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees GROUP BY department_id
) b ON a.department_id = b.department_id
WHERE a.salary > b.avg_sal;
-- CTE 寫法 (更清晰、可重用)
WITH dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees GROUP BY department_id
)
SELECT a.last_name, a.salary, a.department_id
FROM employees a JOIN dept_avg d ON a.department_id = d.department_id
WHERE a.salary > d.avg_sal;
優勢:聚合只掃描一次,轉為 JOIN,優化器易產生 Hash/Merge Join,效能提升數量級。
-- 找出銷售過產品編號以 FW123 開頭的產品的銷售人員
SELECT DISTINCT c.last_name, c.first_name
FROM person.contact c
JOIN humanresources.employee e ON e.contact_id = c.contact_id
WHERE e.employee_id IN (
SELECT sales_person_id FROM sales.salesorderheader
WHERE salesorder_id IN (
SELECT salesorder_id FROM sales.salesorderdetail
WHERE product_id IN (
SELECT product_id FROM production.product WHERE product_number LIKE 'FW123%'
)
)
);
此類多層 IN 子查詢建議改寫為連續 JOIN 或使用 EXISTS,避免重複掃描大表。
NOT IN (subquery) 若子查詢結果含 NULL,整體回傳空集 → **嚴禁在 NOT IN 子查詢中出現 NULL**,改用 NOT EXISTS。NULL,IN 為 FALSE,EXISTS 為 FALSE。