歡迎來到全新的 SQL 講座!今天要來聊聊 最不起眼但超強大的運算子——EXISTS 跟 NOT EXISTS。想像一下有個偵查兵,完全不留痕跡,但馬上回報你:「有,這裡有東西」或「沒有,這裡空空的」。這些運算子不會直接回傳資料,但可以讓你在查詢裡做出 超精確的邏輯判斷。
先從基礎開始。EXISTS 是個用來檢查 子查詢結果有沒有資料 的運算子。如果子查詢有回傳至少一筆資料,EXISTS 條件就會回傳 TRUE,不然就是 FALSE。
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
你看,其實我們根本不在乎子查詢裡的資料長怎樣,只在乎有沒有這樣的資料行。如果有一筆符合條件,查詢就會回傳 1。
EXISTS 的語法
EXISTS 的語法超簡單:
SELECT 欄位
FROM 資料表
WHERE EXISTS (
SELECT 1
FROM 另一個_資料表
WHERE 條件
);
說明:
EXISTS裡的子查詢可以是任何查詢。- 子查詢的結果會決定回傳
TRUE還是FALSE。
範例:有沒有學生分數大於 4?
假設有個 students 資料表:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
假設我們想檢查有沒有學生分數大於 4。可以用這個查詢:
SELECT '有學生分數很高喔!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
結果:
有學生分數很高喔!
為什麼 EXISTS 比 IN 快?
EXISTS 最大的優點就是 只要找到第一筆符合的資料就會停下來。所以如果你只是想檢查有沒有資料,EXISTS 超有效率。
舉例來說,假設 students 資料表有幾百萬筆資料,但我們只想知道有沒有一筆分數大於 4(grade > 4)。SQL 只要找到第一筆符合的資料,查詢就結束了。
使用 NOT EXISTS
現在來聊聊 NOT EXISTS。這個運算子就是 EXISTS 的反義詞。當 子查詢沒有回傳任何資料 時,它才會回傳 TRUE。
範例:找沒有分數(NULL)的學生
假設我們的資料表裡有些學生還沒拿到分數:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
我們想選出所有還沒拿到分數的學生。用 NOT EXISTS:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
結果:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
EXISTS 跟 IN 的比較
有時候你會覺得 EXISTS 跟 IN 好像做一樣的事。表面上是這樣,但其實有細節。尤其當 NULL 混進來時,IN 的行為可能會讓你傻眼,這時 EXISTS 就是救星。
來看個例子。
courses 資料表(可以選的課程):
| course_id | name |
|---|---|
| 1 | 數學 |
| 2 | 歷史 |
然後是學生:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 資料表(誰選了哪門課):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
我們想選出有被選修的課程名稱。看起來很簡單。
用 IN:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
表面上應該沒問題。但如果 enrollments 裡的 courseid 有 NULL,像 Maria Chi 那樣,IN 可能會回傳……什麼都沒有!因為 NULL 讓子查詢「不確定」,SQL 會想:「搞不好 NULL 就是我要找的那個 courseid?」
用 EXISTS:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
但 EXISTS 只會問:「有沒有一筆資料,course_id 一樣?」就這樣。它完全不怕有 NULL,因為它只找具體的對應,不是比對一個值清單。
結論:如果子查詢裡可能有 NULL,建議用 EXISTS,才不會被雷到。
實戰題目範例
students 資料表:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 資料表:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
範例 1. 有選課的學生
來找找有出現在 enrollments 裡的學生(就算像 Maria Chi 那樣怪怪的也算):
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
結果:
Alex Lin
Anna Song
Maria Chi
只要學生有在 enrollments 裡出現過——不管 course_id 是不是 NULL——都會被選出來。
範例 2. 沒有選課的學生
現在來找還沒選課的學生:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
結果:
Dan Seth
Shadow Moon
看來這兩位還沒找到想上的課。或是單純忘記要選課啦 :)
範例 3. 選課人數超過 5 人的課程
courses 資料表:
| course_id | name |
|---|---|
| 1 | 數學 |
| 2 | 歷史 |
| 3 | 生物 |
| 4 | 哲學 |
enrollments 資料表:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
我們想找出 有超過五個學生選的課程。這時 EXISTS 就像在問:「這門課有沒有一組學生超過五個?」
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
結果:
數學
只有「數學」(course_id = 1)有六個學生選。其他課目前還沒這麼熱門。
用 EXISTS 和 NOT EXISTS 常見錯誤
- 搞錯子查詢語法。一定要確認子查詢有正確參照外部資料表。
- 忘記處理
NULL。就算用EXISTS,有時還是要明確處理NULL。 - 子查詢欄位沒加 index。這會讓查詢慢到爆。
今天就到這裡啦!現在你已經會用 EXISTS 跟 NOT EXISTS 來檢查資料存不存在,也知道它們跟 IN 的差別。下次講座我們會繼續深入子查詢,看看怎麼在 SELECT 裡處理聚合資料。
GO TO FULL VERSION