SQL 子查詢完整教學:Subquery 語法、CTE 比較與實戰應用【2026 更新】

SQL 子查詢完整教學:Subquery 語法與實戰應用
子查詢(Subquery)是 SQL 中最強大的功能之一,它允許您在一個查詢中嵌套另一個查詢,解決單一查詢無法處理的複雜問題。本教學將從基礎概念開始,逐步介紹各種子查詢類型,並透過實際案例展示其應用場景。
什麼是子查詢?
子查詢是嵌套在另一個 SQL 語句中的 SELECT 查詢。外層的查詢稱為主查詢(Main Query)或外部查詢(Outer Query),被嵌套的查詢稱為子查詢(Subquery)或內部查詢(Inner Query)。
子查詢的基本語法
-- 基本結構
SELECT column_list
FROM table_name
WHERE column_name operator (
SELECT column_name
FROM table_name
WHERE condition
);
子查詢可以出現的位置
- WHERE 子句中:最常見的用法
- FROM 子句中:作為衍生表格
- SELECT 子句中:作為純量值
- HAVING 子句中:用於分組篩選
子查詢的類型
一、純量子查詢(Scalar Subquery)
純量子查詢只回傳一個單一值(一列一欄),可以用在任何需要單一值的地方。
基本範例:找出高於平均價格的產品
SELECT product_name, price
FROM products
WHERE price > (
SELECT AVG(price)
FROM products
);
在 SELECT 子句中使用純量子查詢
SELECT
employee_name,
salary,
(SELECT AVG(salary) FROM employees) AS avg_salary,
salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;
計算佔比的應用
SELECT
category,
SUM(sales) AS category_sales,
ROUND(
100.0 * SUM(sales) / (SELECT SUM(sales) FROM orders),
2
) AS percentage
FROM orders
GROUP BY category;
二、列子查詢(Row Subquery)
列子查詢回傳單一列(一列多欄),通常用於多欄位比較。
比較多個欄位
SELECT *
FROM products
WHERE (category, brand) = (
SELECT category, brand
FROM products
WHERE product_id = 'P001'
);
找出與特定員工相同部門和職位的同事
SELECT employee_name, department, position
FROM employees
WHERE (department, position) = (
SELECT department, position
FROM employees
WHERE employee_id = 'E100'
)
AND employee_id != 'E100';
三、表格子查詢(Table Subquery)
表格子查詢回傳多列多欄的結果,常與 IN、ANY、ALL 運算子搭配使用,或作為 FROM 子句中的衍生表格。
使用 IN 運算子
-- 找出有下過訂單的客戶
SELECT customer_name, email
FROM customers
WHERE customer_id IN (
SELECT DISTINCT customer_id
FROM orders
WHERE order_date >= '2025-01-01'
);
使用 NOT IN 運算子
-- 找出從未下過訂單的客戶
SELECT customer_name, email
FROM customers
WHERE customer_id NOT IN (
SELECT DISTINCT customer_id
FROM orders
);
⚠️ 注意:使用 NOT IN 時要小心 NULL 值,如果子查詢結果包含 NULL,整個 NOT IN 會回傳空結果。
-- 安全的寫法:排除 NULL
SELECT customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE customer_id IS NOT NULL
);
四、衍生表格(Derived Table)
將子查詢放在 FROM 子句中,作為一個暫時的表格使用。
基本語法
SELECT dt.column1, dt.column2
FROM (
SELECT column1, column2, column3
FROM table_name
WHERE condition
) AS dt -- 衍生表格必須有別名
WHERE dt.column3 > 100;
實際應用:計算每個客戶的訂單統計
SELECT
c.customer_name,
order_stats.order_count,
order_stats.total_amount,
order_stats.avg_amount
FROM customers c
INNER JOIN (
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM orders
GROUP BY customer_id
) AS order_stats ON c.customer_id = order_stats.customer_id
WHERE order_stats.order_count >= 5;
EXISTS 與 NOT EXISTS
EXISTS 是用來檢查子查詢是否回傳任何資料列的運算子,它只關心是否有資料存在,不關心實際內容。
EXISTS 基本語法
SELECT column_list
FROM table1
WHERE EXISTS (
SELECT 1
FROM table2
WHERE table2.foreign_key = table1.primary_key
);
EXISTS vs IN 的差異
使用 IN
-- 找出有訂單的客戶
SELECT customer_name
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
);
使用 EXISTS
-- 相同功能,使用 EXISTS
SELECT customer_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);
效能比較
| 情境 | 建議使用 | 原因 |
|---|---|---|
| 子查詢結果集小 | IN | 直接比對清單 |
| 子查詢結果集大 | EXISTS | 找到第一筆就停止 |
| 外表小、內表大 | EXISTS | 減少子查詢執行次數 |
| 外表大、內表小 | IN | 子查詢只執行一次 |
| 需要處理 NULL | EXISTS | 避免 NULL 問題 |
NOT EXISTS 應用
-- 找出沒有任何訂單的產品
SELECT product_name
FROM products p
WHERE NOT EXISTS (
SELECT 1
FROM order_items oi
WHERE oi.product_id = p.product_id
);
-- 找出所有產品都有庫存的倉庫
SELECT warehouse_name
FROM warehouses w
WHERE NOT EXISTS (
SELECT 1
FROM products p
WHERE NOT EXISTS (
SELECT 1
FROM inventory i
WHERE i.warehouse_id = w.warehouse_id
AND i.product_id = p.product_id
AND i.quantity > 0
)
);
相關子查詢(Correlated Subquery)
相關子查詢是指子查詢中參照了外部查詢的欄位,每處理外部查詢的一列,子查詢就執行一次。
非相關子查詢 vs 相關子查詢
非相關子查詢(獨立執行)
-- 子查詢獨立執行,只執行一次
SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
相關子查詢(依賴外部查詢)
-- 子查詢參照外部的 category,每個類別執行一次
SELECT p.product_name, p.category, p.price
FROM products p
WHERE p.price > (
SELECT AVG(price)
FROM products
WHERE category = p.category -- 參照外部查詢的 p.category
);
相關子查詢實戰範例
找出每個部門薪資最高的員工
SELECT e1.department, e1.employee_name, e1.salary
FROM employees e1
WHERE e1.salary = (
SELECT MAX(e2.salary)
FROM employees e2
WHERE e2.department = e1.department
);
找出訂單金額高於該客戶平均訂單金額的訂單
SELECT o1.order_id, o1.customer_id, o1.total_amount
FROM orders o1
WHERE o1.total_amount > (
SELECT AVG(o2.total_amount)
FROM orders o2
WHERE o2.customer_id = o1.customer_id
);
計算累積排名
SELECT
e1.employee_name,
e1.salary,
(
SELECT COUNT(*)
FROM employees e2
WHERE e2.salary > e1.salary
) + 1 AS salary_rank
FROM employees e1
ORDER BY salary_rank;
ANY 與 ALL 運算子
ANY(SOME)運算子
ANY 表示「任一」,只要子查詢結果中有任一值符合條件即為真。
-- 找出價格大於「任一」電子產品價格的產品
-- 等同於:價格大於電子產品的最低價格
SELECT product_name, price
FROM products
WHERE price > ANY (
SELECT price
FROM products
WHERE category = 'Electronics'
);
ANY 的等價寫法
| 運算式 | 等價於 |
|---|---|
> ANY | > MIN() |
< ANY | < MAX() |
= ANY | IN |
ALL 運算子
ALL 表示「全部」,子查詢結果中的全部值都必須符合條件。
-- 找出價格大於「所有」電子產品價格的產品
-- 等同於:價格大於電子產品的最高價格
SELECT product_name, price
FROM products
WHERE price > ALL (
SELECT price
FROM products
WHERE category = 'Electronics'
);
ALL 的等價寫法
| 運算式 | 等價於 |
|---|---|
> ALL | > MAX() |
< ALL | < MIN() |
<> ALL | NOT IN |
子查詢實戰案例
案例一:客戶價值分析
找出 VIP 客戶(訂單總額前 10%)
WITH customer_totals AS (
SELECT
customer_id,
SUM(total_amount) AS lifetime_value
FROM orders
GROUP BY customer_id
)
SELECT
c.customer_name,
c.email,
ct.lifetime_value
FROM customers c
INNER JOIN customer_totals ct ON c.customer_id = ct.customer_id
WHERE ct.lifetime_value >= (
SELECT PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY lifetime_value)
FROM customer_totals
)
ORDER BY ct.lifetime_value DESC;
案例二:庫存預警
找出庫存量低於平均銷售量的產品
SELECT
p.product_name,
p.stock_quantity,
avg_sales.monthly_avg
FROM products p
INNER JOIN (
SELECT
product_id,
AVG(quantity) AS monthly_avg
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.order_id
WHERE o.order_date >= DATEADD(MONTH, -3, GETDATE())
GROUP BY product_id
) AS avg_sales ON p.product_id = avg_sales.product_id
WHERE p.stock_quantity < avg_sales.monthly_avg * 2;
案例三:連續購買客戶
找出連續三個月都有下單的客戶
WITH monthly_orders AS (
SELECT DISTINCT
customer_id,
FORMAT(order_date, 'yyyy-MM') AS order_month
FROM orders
WHERE order_date >= DATEADD(MONTH, -6, GETDATE())
)
SELECT c.customer_name
FROM customers c
WHERE (
SELECT COUNT(DISTINCT order_month)
FROM monthly_orders mo
WHERE mo.customer_id = c.customer_id
AND order_month IN (
FORMAT(DATEADD(MONTH, -1, GETDATE()), 'yyyy-MM'),
FORMAT(DATEADD(MONTH, -2, GETDATE()), 'yyyy-MM'),
FORMAT(DATEADD(MONTH, -3, GETDATE()), 'yyyy-MM')
)
) = 3;
案例四:產品推薦
找出購買 A 產品的客戶也常購買的其他產品
SELECT
p.product_name,
COUNT(DISTINCT oi.order_id) AS co_purchase_count
FROM order_items oi
INNER JOIN products p ON oi.product_id = p.product_id
WHERE oi.order_id IN (
-- 找出包含產品 A 的訂單
SELECT order_id
FROM order_items
WHERE product_id = 'PROD_A'
)
AND oi.product_id != 'PROD_A' -- 排除產品 A 本身
GROUP BY p.product_name
ORDER BY co_purchase_count DESC
LIMIT 10;
子查詢與 JOIN 的選擇
何時使用子查詢
- 邏輯清晰:問題本身就是「找出符合某條件的...」
- 單一值比較:需要與聚合結果比較
- EXISTS 判斷:檢查是否存在相關記錄
- 複雜過濾:需要多層條件判斷
何時使用 JOIN
- 需要多表欄位:結果需要顯示多個表格的資料
- 效能考量:大部分情況下 JOIN 效能較好
- 簡單關聯:單純的表格連結
同功能的不同寫法比較
題目:找出有訂單的客戶名稱和訂單數
方法一:子查詢
SELECT
customer_name,
(SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.customer_id) AS order_count
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
方法二:JOIN
SELECT
c.customer_name,
COUNT(o.order_id) AS order_count
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name;
方法三:CTE
WITH order_counts AS (
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
)
SELECT c.customer_name, oc.order_count
FROM customers c
INNER JOIN order_counts oc ON c.customer_id = oc.customer_id;
2026 年觀點:子查詢 vs CTE vs Window Function
在現代 SQL 開發中,許多傳統上用子查詢解決的問題,現在有更清晰的替代方案。
何時選擇哪種方案?
| 場景 | 推薦方案 | 原因 |
|---|---|---|
| 簡單的存在性檢查 | EXISTS 子查詢 | 語意清晰、效能好 |
| 計算每組的排名/前 N 名 | Window Function | 一次掃描完成 |
| 多步驟複雜邏輯 | CTE | 可讀性最高 |
| 遞迴查詢(階層結構) | 遞迴 CTE | 唯一選擇 |
| 單一值比較(平均、最大) | 純量子查詢或 CTE | 都可以 |
實際範例比較
題目:找出每個部門薪資最高的員工
方法一:相關子查詢(傳統)
SELECT department, employee_name, salary
FROM employees e1
WHERE salary = (
SELECT MAX(salary)
FROM employees e2
WHERE e2.department = e1.department
);
方法二:Window Function(推薦)
WITH ranked AS (
SELECT department, employee_name, salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
)
SELECT department, employee_name, salary
FROM ranked WHERE rn = 1;
方法三:JOIN + 衍生表格
SELECT e.department, e.employee_name, e.salary
FROM employees e
INNER JOIN (
SELECT department, MAX(salary) AS max_salary
FROM employees GROUP BY department
) m ON e.department = m.department AND e.salary = m.max_salary;
效能比較(一般情況):
- Window Function 通常只需一次表格掃描
- 相關子查詢可能每列執行一次
- JOIN 方法效能介於兩者之間
結論:2026 年的最佳實踐是優先考慮 CTE + Window Function,子查詢保留給語意上更適合的場景。
子查詢效能優化
優化技巧
1. 避免在 SELECT 中使用相關子查詢
-- 較差:每列都執行子查詢
SELECT
p.product_name,
(SELECT SUM(quantity) FROM order_items WHERE product_id = p.product_id) AS total_sold
FROM products p;
-- 較好:使用 JOIN
SELECT
p.product_name,
COALESCE(oi.total_sold, 0) AS total_sold
FROM products p
LEFT JOIN (
SELECT product_id, SUM(quantity) AS total_sold
FROM order_items
GROUP BY product_id
) oi ON p.product_id = oi.product_id;
2. 用 EXISTS 替代 IN(大資料集時)
-- 當子查詢結果很大時,EXISTS 通常更快
-- 替代 IN
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
3. 使用 CTE 提升可讀性和可能的效能
WITH high_value_orders AS (
SELECT customer_id, SUM(total_amount) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(total_amount) > 10000
)
SELECT c.*, hvo.total
FROM customers c
INNER JOIN high_value_orders hvo ON c.customer_id = hvo.customer_id;
結論
子查詢是 SQL 中處理複雜資料需求的重要工具。掌握各種子查詢類型的特性和適用場景,能讓您更靈活地解決實務問題。
關鍵要點回顧
- 純量子查詢:回傳單一值,用於比較運算
- 表格子查詢:配合 IN、ANY、ALL 使用
- EXISTS:檢查存在性,效能通常較好
- 相關子查詢:功能強大但注意效能
- 衍生表格:讓複雜查詢更易讀
隨著經驗累積,您會逐漸培養出選擇子查詢或 JOIN 的直覺,寫出既正確又高效的 SQL 查詢。
需要優化複雜的資料查詢架構? CloudInsight 提供專業的資料庫架構諮詢服務,協助您設計高效能的查詢策略和資料結構,提升系統整體效能。
常見問題
Q1:子查詢和 JOIN 哪個效能比較好?
這取決於具體情況。一般而言,現代資料庫優化器會將許多子查詢改寫為 JOIN 執行,所以效能差異不大。但有幾個原則:(1) EXISTS 在檢查存在性時通常比 IN 快,特別是子查詢結果集大時;(2) 相關子查詢如果在 SELECT 子句中,每列都會執行一次,可能造成效能問題;(3) 實際效能需要查看執行計畫來判斷。建議先寫出邏輯清晰的查詢,再根據執行計畫優化。
Q2:為什麼 NOT IN 遇到 NULL 會有問題?
當子查詢結果包含 NULL 時,NOT IN 會回傳空結果。這是因為 SQL 的三值邏輯:任何值與 NULL 比較都是 UNKNOWN,而 NOT IN 需要所有比較都為 TRUE 才回傳該列。解決方案有三種:(1) 在子查詢中加 WHERE column IS NOT NULL;(2) 改用 NOT EXISTS;(3) 使用 LEFT JOIN 配合 IS NULL 檢查。其中 NOT EXISTS 是最穩健的做法,因為它不受 NULL 值影響。
延伸閱讀
- SQL 基礎語法完整教學 - 從 SELECT 到 JOIN 的完整語法指南
- SQL JOIN 完整教學 - 深入理解各種 JOIN 操作
- SQL Window Function 教學 - 進階分析函數應用
- SQL 效能優化完整指南 - 查詢調校與索引策略
- SQL 進階應用實戰技巧 - 進階 SQL 技術總覽
參考資料
- Microsoft SQL Server Documentation - Subqueries
- PostgreSQL Documentation - Subquery Expressions
- Oracle Database SQL Language Reference - Subqueries
- MySQL Reference Manual - Subqueries
- SQL Performance Explained - Markus Winand
插圖:SQL 子查詢類型總覽
場景描述: 這張資訊圖展示 SQL 子查詢的四種主要類型。畫面分為四個區塊,每個區塊代表一種子查詢類型。左上「純量子查詢」顯示回傳單一值的圖示,標註用於 WHERE 和 SELECT。右上「列子查詢」展示回傳單列多欄的結構,標註用於多欄位比較。左下「表格子查詢」呈現多列多欄的表格形狀,標註搭配 IN、EXISTS 使用。右下「相關子查詢」用雙向箭頭連接內外查詢,標註每列執行一次。中央放置 SQL 子查詢的核心語法結構。配色使用統一的藍色調,每種類型用不同深淺區分。
視覺重點:
- 主要內容清晰呈現
必須出現的元素:
- 依據描述中的關鍵元素
需要顯示的中文字: 無
顏色調性: 專業、清晰
避免元素: 抽象圖形、齒輪、發光特效
Slug:
sql-subquery-types-overview
相關文章
SQL 自學完整指南:學習路徑、AI 輔助工具與證照準備【2026 更新】
2026 年最新 SQL 自學指南:AI 輔助學習工具、免費練習網站、線上課程評比、證照準備攻略。含 Gemini/ChatGPT SQL 助手使用技巧。
SQLSQL 完整指南:從入門到精通的資料庫查詢語言教學【2025最新】
完整 SQL 教學,涵蓋基礎語法、SELECT/JOIN/UPDATE 等查詢指令、SQL Server 安裝設定、SQL Injection 防範。從零開始學會資料庫操作,附練習題與範例程式碼。
SQLSQL 進階技巧:預存程序、觸發器、交易控制實戰教學【2025】
深入學習 SQL 進階功能,包含預存程序 Stored Procedure 建立與最佳實踐、觸發器 Trigger 使用場景、交易控制 Transaction 的 ACID 特性與隔離等級、CTE 通用表格運算式應用。