SQL 子查詢介紹:初學者完整指南

Published March 25, 2025 by 徐培鈞
資料庫

在學習 SQL 的過程中,你可能已經熟悉基本的 SELECTWHEREJOIN 等語法。

但當你開始接觸更複雜的資料分析需求時,你會發現單靠一層查詢已經無法解決問題。

這時候,「子查詢」(Subquery)就成為一個強大的工具。

子查詢能讓你在查詢中「再寫一個查詢」,讓 SQL 更靈活、更強大。

別擔心,這篇文章會從最基礎的概念開始,循序漸進帶你了解子查詢的種類、用法與實例,讓你打下扎實的 SQL 基礎。


什麼是子查詢?

子查詢的定義(Subquery Definition)

子查詢(Subquery),顧名思義,就是在「查詢裡面的查詢」。

你可以把它想像成一段被包在主查詢(Outer Query)中的小查詢,它的任務就是先去資料庫中抓出一個特定的結果,然後把這個結果交給主查詢使用。

子查詢通常會出現在 WHEREFROMSELECT 等語句中,用來提供一個值、一列資料,甚至是一整個資料集,幫助主查詢進行過濾、統計或進一步查詢。

簡單來說:子查詢是幫主查詢「查資料的資料」。

子查詢的邏輯運作方式

在 SQL 執行一段查詢時,會遵循一定的邏輯順序。

當有子查詢時,SQL 引擎會優先執行子查詢,等它的結果產出後,主查詢才會繼續執行並利用這些結果來完成整體的查詢流程。

這樣的設計讓我們可以將查詢拆解成多個階段,有助於處理複雜的邏輯判斷與資料過濾。

例如,當你需要查詢「某個條件成立時的資料」,或「某些統計值的對應結果」,子查詢就特別派得上用場。

flowchart TB
    Start[開始 SQL 查詢] --> SubQ[執行子查詢]
    SubQ --> SubQResult[子查詢返回結果]
    SubQResult --> MainQ[主查詢使用子查詢結果]
    MainQ --> Result[返回最終結果]
    
    subgraph "子查詢執行流程"
        SubQ
        SubQResult
    end
    
    subgraph "主查詢執行流程"
        Start
        MainQ
        Result
    end
    
    style SubQ fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
    style SubQResult fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
    style Start fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px
    style MainQ fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px
    style Result fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px

實例說明:找出屬於 Engineering 部門的員工

我們來看一個具體範例:

SELECT name
FROM employees
WHERE department_id = (
  SELECT id
  FROM departments
  WHERE name = 'Engineering'
);

這段 SQL 做了什麼事?讓我們拆解成兩個部分來看:

🔹 子查詢部分

SELECT id
FROM departments
WHERE name = 'Engineering';

這段查詢會從 departments 表格中找出部門名稱為 Engineering 的部門 ID。假設這個 ID 是 3,這段子查詢的結果就是 3

🔹 主查詢部分

SELECT name
FROM employees
WHERE department_id = 3;

這就是主查詢的實際執行內容。它會去 employees 表格中找出所有 department_id = 3 的員工,也就是屬於 Engineering 部門的員工清單。

✅ 最終效果:

透過子查詢,我們成功地找出了「Engineering 部門的所有員工姓名」,而且不需要先手動查出部門 ID,再進行第二次查詢,讓整個查詢流程變得更自動化、彈性化。

子查詢的使用好處

說明不需要事先知道某個值或結果,子查詢可以幫你即時取得
說明將查詢拆成小單元,讓邏輯更清楚,也方便除錯
說明根據當下資料庫的內容做查詢,不需要硬編數值
說明很多子查詢就像我們日常說的:「找出比平均值高的商品」、「找出訂單最多的顧客」等等,直覺易懂

子查詢的三大分類

子查詢根據它出現的位置不同,可以大致分為三種類型,每一種都對應不同的使用情境與查詢目標。

理解這三種類型,能幫助你靈活運用子查詢來解決各式各樣的資料處理需求。

WHERE 子句中的子查詢

(最常見的子查詢類型)

這是初學者最先接觸到的子查詢類型,也是在實務上最常使用的方式。

你可以將子查詢放在 WHERE 條件中,用來動態篩選出你想要的資料。

語法特性:

  • 通常回傳一個「值」或「值的集合」
  • 常與比較運算子一起使用,如 =, >, <, IN, NOT IN

📌 使用情境:

你想找出所有「大於平均價格」的商品,這時平均價格無法直接在主查詢中得知,就需要子查詢來先算出來。

✅ 範例語法:

SELECT name
FROM products
WHERE price > (
  SELECT AVG(price)
  FROM products
);

這段 SQL 的邏輯如下:

  1. 子查詢 SELECT AVG(price) FROM products 會先計算出產品的平均價格,例如是 100 元。
  2. 然後主查詢就會變成 SELECT name FROM products WHERE price > 100,只顯示價格高於平均值的商品。

⚠️ 注意事項:

  • 子查詢回傳的值必須適用於主查詢中的比較條件。例如,如果子查詢會回傳多筆資料,就不能使用 =,而必須用 IN
  • 若資料表太大,子查詢可能會影響效能,建議搭配索引或改用 JOIN。

FROM 子句中的子查詢

(又稱為「內嵌資料表」或「衍生表」)

這種類型的子查詢會建立一個臨時資料表,讓你可以對這份資料再進行一次查詢或過濾。

這個技巧非常適合應用在分層查詢(multi-level query)中,尤其當你需要先做彙總、分組、計算平均等操作,再進行進一步篩選時特別有用。

語法特性:

  • 子查詢必須取一個暫時的表名(alias)
  • 常用在複雜的統計、彙總、或先查再查的場景中

📌 使用情境:

你想要找出「平均薪資高於 5 萬元的部門名稱」,這就需要先算出每個部門的平均薪資,然後再根據結果進一步篩選。

✅ 範例語法:

SELECT department, avg_salary
FROM (
  SELECT department, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY department
) AS dept_avg
WHERE avg_salary > 50000;

這段查詢的流程:

  1. 子查詢部分先計算每個部門的平均薪資(GROUP BY + AVG)。
  2. 主查詢部分再從這個「臨時資料表」中挑出平均薪資超過 5 萬的部門。

⚠️ 注意事項:

  • 子查詢的結果必須加上別名(alias),如 AS dept_avg,否則 SQL 不知道你要怎麼稱呼這份內嵌表格。
  • 若子查詢太複雜,建議先獨立測試這段查詢結果是否正確,再套用到主查詢中。

SELECT 子句中的子查詢

(適用於每一筆資料需要額外計算時)

這類子查詢會在主查詢的 SELECT 欄位中出現,用來為每一筆主查詢的資料動態加上額外欄位資訊。這是製作報表、統計附加資訊時非常實用的技巧。

語法特性:

  • 子查詢會對主查詢中的每一筆資料進行一次運算
  • 通常用於計算「每個顧客的訂單數」、「每本書的借閱次數」等類型的需求

📌 使用情境:

你想列出所有顧客的名字,並在旁邊加註他們曾經下過多少筆訂單。

✅ 範例語法:

SELECT name,
       (SELECT COUNT(*)
        FROM orders
        WHERE customer_id = customers.id) AS order_count
FROM customers;

這段 SQL 的運作邏輯:

  1. 主查詢從 customers 資料表中抓出每一位顧客的名字。
  2. 每一筆資料,SQL 都會執行一次子查詢:去 orders 資料表中計算這位顧客下過幾筆訂單。
  3. 最後在查詢結果中加入一欄 order_count,顯示每位顧客的訂單數量。

⚠️ 注意事項:

  • 因為每一筆資料都會執行一次子查詢,當主查詢結果很多時,可能會造成效能下降。可以考慮改寫成 JOIN + GROUP BY 來提升效能。
  • 子查詢裡的 WHERE 條件通常會引用主查詢的某個欄位(如 customers.id),這樣才能針對每一筆資料進行個別計算。

哪種子查詢該怎麼用?

使用位置過濾主查詢的資料
適合情境比較條件、過濾特定資料
是否需別名不需要
使用位置建立中間表做進一步查詢
適合情境彙總、群組後再篩選
是否需別名必須取別名
使用位置動態計算每筆資料的額外欄位
適合情境報表統計、個別計算
是否需別名不需要

子查詢的特性與注意事項

雖然子查詢能讓 SQL 查詢變得更強大、彈性更高,但使用時也有一些規則和細節需要特別注意。

了解這些特性,可以幫助你更正確、更有效率地撰寫查詢,避免掉入錯誤陷阱。

子查詢會 先執行,再將結果回傳給主查詢

在一個 SQL 查詢中,當存在子查詢時,資料庫系統會先執行子查詢,取得它的結果,然後再用這個結果套用到主查詢中。

這個執行順序非常重要,因為它影響整體邏輯與效能表現。

✅ 示意範例:

SELECT name
FROM employees
WHERE department_id = (
  SELECT id
  FROM departments
  WHERE name = 'Sales'
);

這裡會先執行子查詢:

SELECT id FROM departments WHERE name = 'Sales';

假設回傳的結果是 5,那麼整個查詢就等同於:

SELECT name FROM employees WHERE department_id = 5;

📌 實務提醒:

  • 子查詢若撰寫錯誤(例如條件模糊、邏輯不明確),整體查詢也會跟著失敗。
  • 若子查詢是動態的(例如使用聚合函數或與其他資料表連結),那麼每次查詢都可能得到不同的結果,必須確認這樣的設計是否合理。

子查詢的回傳型態:標量、單行、多行

不同的子查詢,會依照查詢內容回傳不同型態的資料,這會直接影響你能使用的比較方式與語法。

🔹 標量子查詢(Scalar Subquery)

📌 定義:

標量子查詢是最簡單的一種類型。

它的特色是:只回傳一個值、一個欄位、一筆資料

這個值可以是一個數字、文字、日期,總之就是單一資料點。

因為回傳的結果就像一個普通的常數,所以它可以被直接拿來比較、加減、列印,非常適合搭配 WHERESELECT 子句使用。

🧠 思考邏輯:

當你想要比較某個欄位值是否「大於某個統計值」(像是平均、最大、最小),但這個統計值是要先算出來的,那就可以用標量子查詢來幫忙。

✅ 常見用法:

  • 比較是否大於 / 小於全體的平均值
  • 檢查是否等於某筆統計計算的結果
  • 用來取得某個單一條件下的值(例如某位使用者的 Email)

✅ 範例語法:

SELECT name
FROM products
WHERE price > (
  SELECT AVG(price)
  FROM products
);

✅ 查詢邏輯解析:

  1. 子查詢:SELECT AVG(price) FROM products
    → 這會計算出 products 表中所有商品的平均價格(假設為 100 元)。
  2. 主查詢:SELECT name FROM products WHERE price > 100
    → 接著找出那些價格高於平均值的商品名稱。

🔍 延伸應用:

也可以在 SELECT 欄位中使用,讓每一筆資料附上一個整體統計的值:

SELECT name, price,
       (SELECT AVG(price) FROM products) AS average_price
FROM products;

這樣會顯示每一筆商品的價格,同時附上總體平均價格作為比較參考。

🔹 單行子查詢(Single-row Subquery)

📌 定義:

單行子查詢的回傳結果是「一整列資料(可能多欄)」。

這種類型的子查詢,回傳的資料筆數必須為 1 筆,但可以包含多個欄位。

由於它是「一列」資料,所以可以與主查詢進行多欄位的比較,但條件是兩邊的欄位數量與順序必須相同

🧠 思考邏輯:

當你想要把某筆資料作為比對基準,例如你要查出「跟 Alice 一樣部門與職稱的所有人」,那就需要先透過子查詢取得 Alice 的資訊,然後主查詢再做匹配。

✅ 常見運算子:

  • =
  • <>
  • <, >, <=, >=(數值欄位時)

這些都只能用在「單一筆資料」的比較上。

✅ 範例語法:

SELECT name
FROM employees
WHERE (department_id, job_title) = (
  SELECT department_id, job_title
  FROM employees
  WHERE name = 'Alice'
);

✅ 查詢邏輯解析:

  1. 子查詢:
    SELECT department_id, job_title FROM employees WHERE name = 'Alice'
    → 這會找出 Alice 所在的部門與職稱(例如:部門 2、職稱 “Sales Manager”)
  2. 主查詢:
    會找出所有與 Alice 同部門且同職稱的員工

⚠️ 注意事項:

  • 子查詢一定只能回傳一筆資料,否則會報錯:
    ERROR: more than one row returned by a subquery used as an expression
  • 如果有可能會有重複值(例如員工重名),請加上 LIMIT 1 或額外條件避免回傳多筆資料。

🔹 多行子查詢(Multi-row Subquery)

📌 定義:

多行子查詢是指回傳一欄多列資料的查詢。這類子查詢不能= 來比對,因為 = 只適用於單一值,而這裡會回傳好幾個值。

因此,當你預期子查詢會回傳多筆資料時,應該使用以下這些運算子來做比較:

✅ 常用的運算子:

功能判斷主查詢的欄位值是否存在於子查詢結果中
功能主查詢的值只要符合子查詢結果中的任一個就成立
功能主查詢的值要同時符合子查詢結果的全部條件

🧠 思考邏輯:

當你要找出「符合某個條件集合」的資料時,例如:

  • 哪些學生有修課?
  • 哪些產品曾被下單?
  • 哪些員工參加過某個培訓?

這些條件的背後,都代表一個「多筆資料的結果集合」,就適合使用多行子查詢。

✅ 範例語法(使用 IN):

SELECT name
FROM students
WHERE id IN (
  SELECT student_id
  FROM enrollments
);

✅ 查詢邏輯解析:

  1. 子查詢:SELECT student_id FROM enrollments
    → 回傳所有有修課的學生 ID(可能是 1, 3, 5, 7)
  2. 主查詢:SELECT name FROM students WHERE id IN (1, 3, 5, 7)
    → 找出這些學生的名字

🔍 延伸用法(使用 ANY):

SELECT name
FROM employees
WHERE salary > ANY (
  SELECT salary
  FROM employees
  WHERE department = 'Marketing'
);

這代表:「找出薪資高於『行銷部任一位員工』的所有人」

⚠️ 注意事項:

  • 使用 IN 是最常見、最直觀的寫法,但要注意空集合(empty set)時不會報錯,只會查不到結果。
  • ANYALL 有更靈活的邏輯比較,但使用時要小心語意上的混淆。

📚 小結:三種類型一次看懂

回傳結果一個值
常用語法位置WHERE, SELECT
適合運算子=, >, < 等
用途說明用於比較或直接顯示統計值
回傳結果一列多欄
常用語法位置WHERE, HAVING
適合運算子=, <, >
用途說明比對特定單筆資料的多欄資訊
回傳結果一欄多列
常用語法位置WHERE, HAVING
適合運算子IN, ANY, ALL
用途說明用於匹配多個可能值的條件

子查詢不能亂用 =:看清楚回傳幾筆資料!

這是初學者最容易犯的錯誤之一。

當子查詢可能回傳多筆資料時,如果你寫的是 = 而不是 IN,資料庫會出錯,提示你:「子查詢回傳了多於一筆資料」。

⚠️ 錯誤寫法(子查詢回傳多筆資料,但用了 =):

SELECT name
FROM students
WHERE id = (
  SELECT student_id
  FROM enrollments
);

如果 enrollments 表裡有多位學生,這個子查詢會回傳多筆 student_id,但主查詢只預期一個值,導致錯誤。

✅ 正確寫法(使用 IN 接收多筆回傳):

SELECT name
FROM students
WHERE id IN (
  SELECT student_id
  FROM enrollments
);

這裡用 IN 表示「id 是這些 student_id 中的任一個」,適用於多行子查詢的情境。

延伸說明:什麼時候用 INANYALL

意義等於列表中任一個值
使用情境多筆資料匹配
意義與任何一筆資料比較成立即可
使用情境某值大於所有子查詢值之一
意義與所有資料都需符合比較條件
使用情境某值需大於子查詢的所有值

✅ 範例:

-- 找出薪資高於任一位 IT 部門員工的員工
SELECT name
FROM employees
WHERE salary > ANY (
  SELECT salary
  FROM employees
  WHERE department = 'IT'
);

子查詢 vs JOIN:我應該用哪個?

當你學會了子查詢(Subquery)之後,往往會遇到這個經典問題:

「欸,可是我用 JOIN 好像也能做到一樣的事情,那我到底應該用哪一個?」

這個問題問得非常好,因為子查詢和 JOIN 都能處理多張資料表之間的關聯與查詢邏輯,但它們的適用時機、可讀性、效能與使用習慣其實都有差異。

理解兩者的差別與各自的優勢,可以幫助你根據實際需求選擇最合適的寫法。

使用子查詢的情境

子查詢通常更適合在以下這幾種狀況下使用:

1. 查詢邏輯清楚、層次分明的時候

如果你的查詢有明確的「先做什麼、再根據這個結果做什麼」的步驟,子查詢會讓整體邏輯看起來更直觀、類似自然語言描述。

範例:找出高於平均價格的商品

SELECT name
FROM products
WHERE price > (
  SELECT AVG(price)
  FROM products
);

這段查詢邏輯就是:先算平均 → 再比大小 → 選出結果,步驟一目了然。

2. 子查詢的結果只會被使用一次

如果你只需要這段資料用在一個地方(例如條件篩選),而不需要重複引用,那子查詢會更簡潔,少了 JOIN 的複雜結構。

範例:找出屬於 “Sales” 部門的員工

SELECT name
FROM employees
WHERE department_id = (
  SELECT id
  FROM departments
  WHERE name = 'Sales'
);

這裡子查詢只回傳一個部門 ID 給主查詢使用,不需要 JOIN。

3. 不需要同時取用多個資料表的欄位

如果你只需要外表的欄位,而子查詢只用來提供條件,就可以避免 JOIN 的多欄位管理,讓 SQL 更簡潔。

使用 JOIN 的情境

JOIN 是 SQL 中專門用來結合多張表的工具,當你要處理跨表查詢、多欄位引用時,JOIN 會更適合。

1. 需要同時查詢多張資料表的欄位

當你不只是要從兩張表取條件,還要「取出來一起顯示」的時候,JOIN 就會比較直觀有效。

範例:列出每位員工的部門名稱

SELECT employees.name, departments.name AS department_name
FROM employees
JOIN departments
  ON employees.department_id = departments.id;

這樣可以一次取出員工名稱與對應部門,不需要額外的子查詢。

2. 資料表之間關聯度高(例如主從關係)

像是「顧客與訂單」、「學生與課程」、「產品與分類」這種天然有關聯的資料,JOIN 可以快速整合關聯資料,更方便進行彙總、分析。

3. 效能考量:JOIN 在大型資料量時通常效能更好

在許多資料庫引擎中(如 PostgreSQL、MySQL、SQL Server 等),JOIN 的執行計劃通常比子查詢更有效率,尤其在子查詢被多次重複執行時。

特別是當子查詢中沒有適當索引,或用在 SELECT 子句內導致每一筆都重複執行一次時,效能就會顯著下降。

實際對照範例:子查詢 vs JOIN

目標:列出所有金額高於該顧客平均訂單金額的訂單

🔍 假設我們的資料表 orders 長這樣:

customer_id101
amount1000
customer_id101
amount3000
customer_id102
amount2000
customer_id102
amount4000
customer_id103
amount1500

🔹 使用子查詢寫法(每一筆比對一次平均)

SELECT id, customer_id, amount
FROM orders
WHERE amount > (
  SELECT AVG(amount)
  FROM orders AS o2
  WHERE o2.customer_id = orders.customer_id
);

🧠 執行邏輯說明:

  • 假設現在外層主查詢正在看第 1 筆資料(id = 1, customer_id = 101, amount = 1000)
  • 內層子查詢會這樣執行:
SELECT AVG(amount)
FROM orders AS o2
WHERE o2.customer_id = 101;
-- 結果是:AVG(1000, 3000) = 2000
  • 比較:1000 > 2000 ❌ 不成立 → 不會顯示這筆

換成第 2 筆資料(id = 2, customer_id = 101, amount = 3000):

SELECT AVG(amount)
FROM orders AS o2
WHERE o2.customer_id = 101;
-- 一樣是 2000
  • 比較:3000 > 2000 ✅ 成立 → 顯示出這筆

如此類推,子查詢會針對每一筆訂單執行一次,即使是同一位顧客,也會重新算平均,這樣在資料筆數多時會拖慢效能。

✅ 最後查詢結果會是:

customer_id101
amount3000
customer_id102
amount4000

🔹 使用 JOIN 寫法(先算好平均一次性 JOIN)

SELECT o.id, o.customer_id, o.amount
FROM orders o
JOIN (
  SELECT customer_id, AVG(amount) AS avg_amount
  FROM orders
  GROUP BY customer_id
) AS avg_table
  ON o.customer_id = avg_table.customer_id
WHERE o.amount > avg_table.avg_amount;

🧠 執行邏輯說明:

  1. 子查詢 avg_table: 先幫每個顧客算好平均金額
avg_amount2000
avg_amount3000
avg_amount1500
  1. 主查詢 JOIN: 把每筆訂單的 customer_id 與這個 avg_table 對應起來
  2. 比對 o.amount > avg_table.avg_amount
  • 訂單 id=1:1000 > 2000 ❌ 不成立
  • 訂單 id=2:3000 > 2000 ✅ 成立
  • 訂單 id=3:2000 > 3000 ❌ 不成立
  • 訂單 id=4:4000 > 3000 ✅ 成立
  • 訂單 id=5:1500 > 1500 ❌ 不成立

✅ 最後查詢結果會是:

customer_id101
amount3000
customer_id102
amount4000

和子查詢的結果相同,但效能更好,尤其在顧客數量很大時。

🔹 比較總結

子查詢寫法較直觀,但會讓人疑惑 o2.customer_id = orders.customer_id
JOIN 寫法結構清楚,資料流程分明
子查詢寫法每筆訂單都重新計算平均,效能差
JOIN 寫法平均值先算好,再整批 JOIN,效能佳
子查詢寫法小量資料、單次條件比對
JOIN 寫法大量資料、報表類查詢、效能優化需求
子查詢寫法子查詢要引用外層欄位,初學者易混淆
JOIN 寫法JOIN 寫法長但比較模組化、可擴展

如果你是初學者,我會建議你:

  • 先學會子查詢邏輯 → 再練習 JOIN 的寫法與優化
  • 當你資料表愈來愈大、查詢變複雜,就會更常使用 JOIN

結語:子查詢是 SQL 的秘密武器

學會子查詢後,你會發現資料查詢的世界大大不同。

它不只是讓你寫出更強大的查詢語法,更重要的是幫你建立清晰、有邏輯的思維方式。

對初學者來說,剛開始可能會覺得子查詢有點繞,但只要多看、多寫、多練習,很快你就能掌握它的威力。

記住,每一段子查詢都是一個小工具,熟練掌握後,你的 SQL 寫作能力就能邁上一個新台階。