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

更新日期: 2025 年 3 月 25 日

在學習 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),這樣才能針對每一筆資料進行個別計算。

哪種子查詢該怎麼用?

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

子查詢的特性與注意事項

雖然子查詢能讓 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判斷主查詢的欄位值是否存在於子查詢結果中
ANY主查詢的值只要符合子查詢結果中的任一個就成立
ALL主查詢的值要同時符合子查詢結果的全部條件

🧠 思考邏輯:

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

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

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

✅ 範例語法(使用 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, HAVINGIN, 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

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

✅ 範例:

-- 找出薪資高於任一位 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 長這樣:

idcustomer_idamount
11011000
21013000
31022000
41024000
51031500

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

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 ✅ 成立 → 顯示出這筆

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

✅ 最後查詢結果會是:

idcustomer_idamount
21013000
41024000

🔹 使用 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: 先幫每個顧客算好平均金額
customer_idavg_amount
1012000
1023000
1031500
  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 ❌ 不成立

✅ 最後查詢結果會是:

idcustomer_idamount
21013000
41024000

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

🔹 比較總結

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

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

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

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

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

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

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

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

Similar Posts