SQL 子查詢介紹:初學者完整指南
更新日期: 2025 年 3 月 25 日
在學習 SQL 的過程中,你可能已經熟悉基本的 SELECT
、WHERE
、JOIN
等語法。
但當你開始接觸更複雜的資料分析需求時,你會發現單靠一層查詢已經無法解決問題。
這時候,「子查詢」(Subquery)就成為一個強大的工具。
子查詢能讓你在查詢中「再寫一個查詢」,讓 SQL 更靈活、更強大。
別擔心,這篇文章會從最基礎的概念開始,循序漸進帶你了解子查詢的種類、用法與實例,讓你打下扎實的 SQL 基礎。
什麼是子查詢?
子查詢的定義(Subquery Definition)
子查詢(Subquery),顧名思義,就是在「查詢裡面的查詢」。
你可以把它想像成一段被包在主查詢(Outer Query)中的小查詢,它的任務就是先去資料庫中抓出一個特定的結果,然後把這個結果交給主查詢使用。
子查詢通常會出現在 WHERE
、FROM
或 SELECT
等語句中,用來提供一個值、一列資料,甚至是一整個資料集,幫助主查詢進行過濾、統計或進一步查詢。
簡單來說:子查詢是幫主查詢「查資料的資料」。
子查詢的邏輯運作方式
在 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 的邏輯如下:
- 子查詢
SELECT AVG(price) FROM products
會先計算出產品的平均價格,例如是 100 元。 - 然後主查詢就會變成
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;
這段查詢的流程:
- 子查詢部分先計算每個部門的平均薪資(GROUP BY + AVG)。
- 主查詢部分再從這個「臨時資料表」中挑出平均薪資超過 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 的運作邏輯:
- 主查詢從
customers
資料表中抓出每一位顧客的名字。 - 每一筆資料,SQL 都會執行一次子查詢:去
orders
資料表中計算這位顧客下過幾筆訂單。 - 最後在查詢結果中加入一欄
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)
📌 定義:
標量子查詢是最簡單的一種類型。
它的特色是:只回傳一個值、一個欄位、一筆資料。
這個值可以是一個數字、文字、日期,總之就是單一資料點。
因為回傳的結果就像一個普通的常數,所以它可以被直接拿來比較、加減、列印,非常適合搭配 WHERE
或 SELECT
子句使用。
🧠 思考邏輯:
當你想要比較某個欄位值是否「大於某個統計值」(像是平均、最大、最小),但這個統計值是要先算出來的,那就可以用標量子查詢來幫忙。
✅ 常見用法:
- 比較是否大於 / 小於全體的平均值
- 檢查是否等於某筆統計計算的結果
- 用來取得某個單一條件下的值(例如某位使用者的 Email)
✅ 範例語法:
SELECT name
FROM products
WHERE price > (
SELECT AVG(price)
FROM products
);
✅ 查詢邏輯解析:
- 子查詢:
SELECT AVG(price) FROM products
→ 這會計算出products
表中所有商品的平均價格(假設為 100 元)。 - 主查詢:
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'
);
✅ 查詢邏輯解析:
- 子查詢:
SELECT department_id, job_title FROM employees WHERE name = 'Alice'
→ 這會找出 Alice 所在的部門與職稱(例如:部門 2、職稱 “Sales Manager”) - 主查詢:
會找出所有與 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
);
✅ 查詢邏輯解析:
- 子查詢:
SELECT student_id FROM enrollments
→ 回傳所有有修課的學生 ID(可能是 1, 3, 5, 7) - 主查詢:
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)時不會報錯,只會查不到結果。 ANY
和ALL
有更靈活的邏輯比較,但使用時要小心語意上的混淆。
📚 小結:三種類型一次看懂
子查詢類型 | 回傳結果 | 常用語法位置 | 適合運算子 | 用途說明 |
---|---|---|---|---|
標量子查詢 | 一個值 | 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 中的任一個」,適用於多行子查詢的情境。
延伸說明:什麼時候用 IN
、ANY
、ALL
?
關鍵字 | 意義 | 使用情境 |
---|---|---|
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
長這樣:
id | customer_id | amount |
---|---|---|
1 | 101 | 1000 |
2 | 101 | 3000 |
3 | 102 | 2000 |
4 | 102 | 4000 |
5 | 103 | 1500 |
🔹 使用子查詢寫法(每一筆比對一次平均)
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
✅ 成立 → 顯示出這筆
如此類推,子查詢會針對每一筆訂單執行一次,即使是同一位顧客,也會重新算平均,這樣在資料筆數多時會拖慢效能。
✅ 最後查詢結果會是:
id | customer_id | amount |
---|---|---|
2 | 101 | 3000 |
4 | 102 | 4000 |
🔹 使用 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;
🧠 執行邏輯說明:
- 子查詢 avg_table: 先幫每個顧客算好平均金額
customer_id | avg_amount |
---|---|
101 | 2000 |
102 | 3000 |
103 | 1500 |
- 主查詢 JOIN: 把每筆訂單的
customer_id
與這個avg_table
對應起來 - 比對
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 ❌ 不成立
✅ 最後查詢結果會是:
id | customer_id | amount |
---|---|---|
2 | 101 | 3000 |
4 | 102 | 4000 |
和子查詢的結果相同,但效能更好,尤其在顧客數量很大時。
🔹 比較總結
比較項目 | 子查詢寫法 | JOIN 寫法 |
---|---|---|
可讀性 | 較直觀,但會讓人疑惑 o2.customer_id = orders.customer_id | 結構清楚,資料流程分明 |
執行效率 | 每筆訂單都重新計算平均,效能差 | 平均值先算好,再整批 JOIN,效能佳 |
適用情境 | 小量資料、單次條件比對 | 大量資料、報表類查詢、效能優化需求 |
使用難點 | 子查詢要引用外層欄位,初學者易混淆 | JOIN 寫法長但比較模組化、可擴展 |
如果你是初學者,我會建議你:
- 先學會子查詢邏輯 → 再練習 JOIN 的寫法與優化
- 當你資料表愈來愈大、查詢變複雜,就會更常使用 JOIN
結語:子查詢是 SQL 的秘密武器
學會子查詢後,你會發現資料查詢的世界大大不同。
它不只是讓你寫出更強大的查詢語法,更重要的是幫你建立清晰、有邏輯的思維方式。
對初學者來說,剛開始可能會覺得子查詢有點繞,但只要多看、多寫、多練習,很快你就能掌握它的威力。
記住,每一段子查詢都是一個小工具,熟練掌握後,你的 SQL 寫作能力就能邁上一個新台階。