Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

跨表單查詢:用子查詢做篩選條件(先計算的做法)

最後更新:2026年1月30日資料庫

在上一篇文章,我們介紹了兩種跨表查詢的策略:「先計算」和「先合併」。

這篇文章來實作「先計算」的做法,也就是把子查詢放在篩選條件中。

回顧一下任務

老闆說:「幫我找出平均客單價 5 萬以上的會員,然後給我他們的信箱。」

用「先計算」的策略,步驟是這樣:

  1. 先在訂單表算出平均客單價超過 5 萬的會員 ID
  2. 把這些 ID 當成篩選條件,去會員表查信箱

第一步:從訂單表找出符合條件的會員 ID

我們要從訂單表中,找出平均客單價超過 5 萬的會員 ID。

這裡用到「分組查詢」,如果不熟悉的話,建議先回去看上一個章節。

SELECT 會員ID
FROM 訂單
GROUP BY 會員ID
HAVING AVG(金額) > 50000

執行後會得到:

會員ID
2

只有會員 ID = 2(Mary)的平均客單價超過 5 萬。

為什麼用 HAVING 不用 WHERE?

因為我們要篩選的是「分組之後」的結果。

SQL 的執行順序是這樣的:

  1. FROM — 先決定從哪張表撈資料
  2. WHERE — 篩選原始資料
  3. GROUP BY — 把資料分組
  4. HAVING — 篩選分組後的結果
  5. SELECT — 決定要顯示哪些欄位

你會發現,WHERE 在 GROUP BY 之前執行,HAVING 在 GROUP BY 之後執行。

而且,WHERE 是「一筆一筆」去看資料,決定這筆要不要保留。

在看某一筆資料的時候,它沒辦法參考其他筆資料。

但「平均金額」需要把同一個會員的所有訂單加起來再除以筆數,這需要參考多筆資料。

在 WHERE 執行的時候,還沒分組,它只能看到單筆資料,根本算不出「平均金額」。

所以這裡只能用 HAVING,等分組完成後再篩選。

這個查詢結果會變成「子查詢」

等一下,我們會把這整段查詢,塞進另一個查詢的篩選條件裡。

這種「查詢裡面包查詢」的結構,內層的查詢就叫做「子查詢」。

第二步:用這些 ID 去會員表查信箱

現在我們有了一個子查詢,它會回傳符合條件的會員 ID。

接下來,把這個子查詢當成篩選條件,去會員表查資料。

先看簡化的寫法:

SELECT 信箱
FROM 會員
WHERE 會員ID IN (子查詢)

把子查詢帶入:

SELECT 信箱
FROM 會員
WHERE 會員ID IN (
    SELECT 會員ID
    FROM 訂單
    GROUP BY 會員ID
    HAVING AVG(金額) > 50000
)

這段在做什麼?

  1. 子查詢:找出平均客單價超過 5 萬的會員 ID(結果是 2)
  2. 外層查詢:從會員表挑出信箱,條件是會員 ID 要在子查詢的結果裡

用白話講就是:

從會員表中,找出「會員 ID 包含在 (2) 裡面」的人,然後回傳他的信箱。

為什麼用 IN 不用 =?

因為子查詢可能回傳多個 ID。

雖然這個例子只有一個人符合條件,但實際情況可能有很多人。

用 IN 可以處理「一個或多個」的情況:

WHERE 會員ID IN (2)        -- 一個 ID
WHERE 會員ID IN (1, 2, 3)  -- 多個 ID

為什麼子查詢只能挑一個欄位?

因為 IN 會把子查詢的結果當成「一串值」來比對。

子查詢回傳的其實是一個表格:

會員ID
2

IN 會把這個「只有一欄」的表格,當成一串值 (2) 來用。

如果子查詢挑了兩個欄位:

SELECT 會員ID, AVG(金額) FROM 訂單 ...

結果會是:

會員ID平均金額
255000
平均金額55000

這是一個「兩欄」的表格,IN 不知道要用哪一欄來比對,所以會報錯。

簡單說:IN 只能處理「單欄」的表格。

為什麼挑的是會員 ID?

因為我們要用這個值去會員表「找人」。

會員表和訂單表之間的關聯就是「會員 ID」,這是兩張表的共同欄位。

只有用會員 ID,才能在會員表中找到對應的那一列資料。

執行結果

執行後會得到:

信箱
mary@mail.com

簡單暴力,任務完成!

這個方法的限制

雖然簡單,但你有沒有發現一個問題?

最後的結果只有「信箱」這個欄位。

如果老闆接著問:「那 Mary 的平均客單價是多少?」

你答不出來。

為什麼細節會消失?

回想一下第二步的查詢:

SELECT 信箱
FROM 會員
WHERE 會員ID IN (
    SELECT 會員ID
    FROM 訂單
    GROUP BY 會員ID
    HAVING AVG(金額) > 50000
)

子查詢回傳的是一個表格:

會員ID
2

外層查詢拿到這個結果後,做的事情是:

「會員 ID = 1,有沒有在 (2) 裡面?沒有,跳過。」

「會員 ID = 2,有沒有在 (2) 裡面?有,保留這筆,取出信箱。」

「會員 ID = 3,有沒有在 (2) 裡面?沒有,跳過。」

你會發現,外層查詢只是拿子查詢的結果來「比對」,它不知道也不關心:

  • 會員 ID = 2 的平均客單價是多少?
  • 他買了幾筆訂單?
  • 每筆訂單的金額是多少?

這些資訊都留在子查詢裡,沒有被帶到外層來。

所以最後的結果只有「信箱」,你沒辦法回推「Mary 的平均客單價是 55,000」。

這就是「先計算」的代價:另一張表的細節全部消失。

小結

  • 「先計算」的做法:把子查詢放在篩選條件中
  • 優點:簡單直觀,邏輯是「算一次 → 篩選一次」
  • 缺點:另一張表的欄位會變成布林值,細節全部消失
  • 適合情境:只需要最終結果,不需要細節

下一篇文章,我們會介紹「先合併」的做法,也就是用 JOIN 把兩張表合在一起。

目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

資料庫

目錄

  • 回顧一下任務
  • 第一步:從訂單表找出符合條件的會員 ID
  • 為什麼用 HAVING 不用 WHERE?
  • 這個查詢結果會變成「子查詢」
  • 第二步:用這些 ID 去會員表查信箱
  • 這段在做什麼?
  • 為什麼用 IN 不用 =?
  • 為什麼子查詢只能挑一個欄位?
  • 為什麼挑的是會員 ID?
  • 執行結果
  • 這個方法的限制
  • 為什麼細節會消失?
  • 小結