返回首頁SQL

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

19 min 分鐘閱讀
#SQL#子查詢#Subquery#CTE#資料庫#效能優化

SQL 子查詢完整教學:Subquery 語法與實戰應用

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
);

子查詢可以出現的位置

  1. WHERE 子句中:最常見的用法
  2. FROM 子句中:作為衍生表格
  3. SELECT 子句中:作為純量值
  4. 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子查詢只執行一次
需要處理 NULLEXISTS避免 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()
= ANYIN

ALL 運算子

ALL 表示「全部」,子查詢結果中的全部值都必須符合條件。

-- 找出價格大於「所有」電子產品價格的產品
-- 等同於:價格大於電子產品的最高價格
SELECT product_name, price
FROM products
WHERE price > ALL (
    SELECT price
    FROM products
    WHERE category = 'Electronics'
);

ALL 的等價寫法

運算式等價於
> ALL> MAX()
< ALL< MIN()
<> ALLNOT 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 的選擇

何時使用子查詢

  1. 邏輯清晰:問題本身就是「找出符合某條件的...」
  2. 單一值比較:需要與聚合結果比較
  3. EXISTS 判斷:檢查是否存在相關記錄
  4. 複雜過濾:需要多層條件判斷

何時使用 JOIN

  1. 需要多表欄位:結果需要顯示多個表格的資料
  2. 效能考量:大部分情況下 JOIN 效能較好
  3. 簡單關聯:單純的表格連結

同功能的不同寫法比較

題目:找出有訂單的客戶名稱和訂單數

方法一:子查詢

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 中處理複雜資料需求的重要工具。掌握各種子查詢類型的特性和適用場景,能讓您更靈活地解決實務問題。

關鍵要點回顧

  1. 純量子查詢:回傳單一值,用於比較運算
  2. 表格子查詢:配合 IN、ANY、ALL 使用
  3. EXISTS:檢查存在性,效能通常較好
  4. 相關子查詢:功能強大但注意效能
  5. 衍生表格:讓複雜查詢更易讀

隨著經驗累積,您會逐漸培養出選擇子查詢或 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 值影響。

延伸閱讀

參考資料

  1. Microsoft SQL Server Documentation - Subqueries
  2. PostgreSQL Documentation - Subquery Expressions
  3. Oracle Database SQL Language Reference - Subqueries
  4. MySQL Reference Manual - Subqueries
  5. SQL Performance Explained - Markus Winand

插圖:SQL 子查詢類型總覽

場景描述: 這張資訊圖展示 SQL 子查詢的四種主要類型。畫面分為四個區塊,每個區塊代表一種子查詢類型。左上「純量子查詢」顯示回傳單一值的圖示,標註用於 WHERE 和 SELECT。右上「列子查詢」展示回傳單列多欄的結構,標註用於多欄位比較。左下「表格子查詢」呈現多列多欄的表格形狀,標註搭配 IN、EXISTS 使用。右下「相關子查詢」用雙向箭頭連接內外查詢,標註每列執行一次。中央放置 SQL 子查詢的核心語法結構。配色使用統一的藍色調,每種類型用不同深淺區分。

視覺重點:

  • 主要內容清晰呈現

必須出現的元素:

  • 依據描述中的關鍵元素

需要顯示的中文字:

顏色調性: 專業、清晰

避免元素: 抽象圖形、齒輪、發光特效

Slug: sql-subquery-types-overview


需要專業的雲端建議?

無論您正在評估雲平台、優化現有架構,或尋找節費方案,我們都能提供協助

預約免費諮詢

相關文章