在上一篇文章,我們介紹了兩種跨表查詢的策略:「先計算」和「先合併」。
這篇文章來實作「先計算」的做法,也就是把子查詢放在篩選條件中。
回顧一下任務
老闆說:「幫我找出平均客單價 5 萬以上的會員,然後給我他們的信箱。」
用「先計算」的策略,步驟是這樣:
- 先在訂單表算出平均客單價超過 5 萬的會員 ID
- 把這些 ID 當成篩選條件,去會員表查信箱
第一步:從訂單表找出符合條件的會員 ID
我們要從訂單表中,找出平均客單價超過 5 萬的會員 ID。
這裡用到「分組查詢」,如果不熟悉的話,建議先回去看上一個章節。
SELECT 會員ID
FROM 訂單
GROUP BY 會員ID
HAVING AVG(金額) > 50000執行後會得到:
| 會員ID |
|---|
| 2 |
只有會員 ID = 2(Mary)的平均客單價超過 5 萬。
為什麼用 HAVING 不用 WHERE?
因為我們要篩選的是「分組之後」的結果。
SQL 的執行順序是這樣的:
- FROM — 先決定從哪張表撈資料
- WHERE — 篩選原始資料
- GROUP BY — 把資料分組
- HAVING — 篩選分組後的結果
- 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
)這段在做什麼?
- 子查詢:找出平均客單價超過 5 萬的會員 ID(結果是 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 | 平均金額 |
|---|---|
| 2 | 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 把兩張表合在一起。