一、核心:EXISTS到底是什么?(一句話懂)
EXISTS = “存在”,核心作用:只判斷“有沒有”,不關(guān)心具體有多少條、具體是什么數(shù)據(jù),找到1條符合條件的就停止,返回“有”(true),找不到就返回“沒有”(false)。
通俗類比:找東西時,只要看到1個目標(biāo),就確認(rèn)“有”,不用把所有目標(biāo)都找出來。
學(xué)生場景SQL例子(3個不同角度,易懂不復(fù)雜)
例子1:正向判斷(基礎(chǔ)款,判斷“有”)
需求:查“有考試成績的學(xué)生”(只要有1次成績就顯示,不管分?jǐn)?shù))
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID);
解讀:逐個檢查學(xué)生,只要成績表中有該學(xué)生的任意1條成績,就保留該學(xué)生。
例子2:帶條件的存在(進(jìn)階款,判斷“有符合條件的”)
需求:查“有不及格成績(<60分)的學(xué)生”(只要有1次不及格就顯示)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID AND c.成績 < 60);
解讀:不關(guān)心學(xué)生有多少及格成績,只判斷“有沒有不及格的”,找到1條就保留。
例子3:反向判斷(NOT EXISTS,判斷“沒有”)
需求:查“沒有請假記錄的學(xué)生”(全程無請假,才顯示)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE NOT EXISTS (SELECT 1 FROM 請假表 q WHERE q.學(xué)生ID = s.學(xué)生ID);
解讀:逐個檢查學(xué)生,請假表中找不到該學(xué)生的任何請假記錄,就保留(“沒有”請假,符合條件)。
二、什么時候用EXISTS?(記2個場景,不踩坑)
只需要判斷“是否存在關(guān)聯(lián)數(shù)據(jù)”,不需要具體數(shù)據(jù)(比如:有沒有成績、有沒有訂單);
數(shù)據(jù)量大時(比如學(xué)生表、成績表各10萬條),用EXISTS比IN快。
三、EXISTS vs IN(核心區(qū)別,一眼看懂)
| 對比點 | EXISTS | IN |
|---|---|---|
| 核心邏輯 | 判斷“有沒有”,找到1條就停 | 先查全量子查詢結(jié)果,再匹配 |
| 通俗類比 | 找筆:看到1支就確認(rèn)“有” | 找筆:把所有筆都找出來,再確認(rèn) |
| 效率 | 大數(shù)據(jù)量快(不做無用功) | 大數(shù)據(jù)量慢(需查全量) |
| 適用場景 | 判斷存在性、大數(shù)據(jù)量、子查詢有NULL | 匹配具體數(shù)據(jù)、小數(shù)據(jù)量、子查詢無NULL |
| 查詢結(jié)果 | 不受子查詢NULL影響,結(jié)果穩(wěn)定 | 子查詢有NULL時,結(jié)果會出錯(查不到數(shù)據(jù)) |
關(guān)鍵補充:兩者不是所有情況結(jié)果都一樣,核心差異在「子查詢有NULL值」時,具體看學(xué)生場景例子:
例子4:子查詢有NULL,結(jié)果不同(易踩坑)
需求:查“有成績的學(xué)生”(成績表中部分學(xué)生ID為NULL)
用EXISTS(正確,能查到有成績的學(xué)生):
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID);
用IN(錯誤,查不到任何數(shù)據(jù)):
SELECT 學(xué)生姓名 FROM 學(xué)生表 WHERE 學(xué)生ID IN (SELECT 學(xué)生ID FROM 成績表); -- 子查詢有NULL,IN會返回空結(jié)果
解讀:IN遇到子查詢中的NULL值,會直接返回空結(jié)果(相當(dāng)于查不到任何數(shù)據(jù)),而EXISTS不受NULL影響,正常判斷“有沒有”。
四、明確區(qū)分:什么時候用IN,什么時候用EXISTS?(對比例子版,學(xué)生直接套)
核心:分兩種情況對比,既有「結(jié)果相同」的場景,也有「結(jié)果不同」的場景,結(jié)合例子一看就懂,直接套用即可。
場景1:結(jié)果相同(小數(shù)據(jù)量、子查詢無NULL,兩者都能用,僅效率有差異)
需求:查“有考試成績(子查詢無NULL)的學(xué)生”(小數(shù)據(jù)量:學(xué)生表50人,成績表200條)
方法1:用EXISTS(判斷存在性,效率略高)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID);
方法2:用IN(匹配具體數(shù)據(jù),結(jié)果一致)
SELECT 學(xué)生姓名 FROM 學(xué)生表 WHERE 學(xué)生ID IN (SELECT 學(xué)生ID FROM 成績表);
結(jié)果:兩條SQL查詢結(jié)果完全一致,都能查到所有有成績的學(xué)生。
選擇建議:小數(shù)據(jù)量無所謂,大數(shù)據(jù)量優(yōu)先用EXISTS;想寫起來簡潔,用IN。
場景2:結(jié)果相同(明確匹配具體值,兩者都能用)
需求:查“成績?yōu)?0分或90分的學(xué)生”(子查詢無NULL,小數(shù)據(jù)量)
方法1:用EXISTS(判斷“有符合條件的成績”)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID AND c.成績 IN (80,90));
方法2:用IN(匹配具體學(xué)生ID)
SELECT 學(xué)生姓名 FROM 學(xué)生表 WHERE 學(xué)生ID IN (SELECT 學(xué)生ID FROM 成績表 WHERE 成績 IN (80,90));
結(jié)果:兩條SQL查詢結(jié)果完全一致,都能查到成績?yōu)?0分或90分的學(xué)生。
選擇建議:這種場景用IN更簡潔,寫起來更省事。
場景3:結(jié)果不同(子查詢有NULL,僅EXISTS能用,IN會出錯)
需求:查“有成績的學(xué)生”(成績表中部分學(xué)生ID為NULL,小數(shù)據(jù)量)
方法1:用EXISTS(正確,結(jié)果正常)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 成績表 c WHERE c.學(xué)生ID = s.學(xué)生ID);
結(jié)果:能正常查到所有有成績的學(xué)生。
方法2:用IN(錯誤,無結(jié)果)
SELECT 學(xué)生姓名 FROM 學(xué)生表 WHERE 學(xué)生ID IN (SELECT 學(xué)生ID FROM 成績表);
結(jié)果:查不到任何數(shù)據(jù)(IN遇到子查詢NULL會返回空結(jié)果)。
選擇建議:子查詢有NULL,必須用EXISTS,避免出錯。
場景4:結(jié)果不同(大數(shù)據(jù)量,效率差異明顯,結(jié)果可能一致但體驗不同)
需求:查“有請假記錄的學(xué)生”(大數(shù)據(jù)量:學(xué)生表10萬條,請假表5萬條)
方法1:用EXISTS(高效,1秒出結(jié)果)
SELECT 學(xué)生姓名 FROM 學(xué)生表 s WHERE EXISTS (SELECT 1 FROM 請假表 q WHERE q.學(xué)生ID = s.學(xué)生ID);
方法2:用IN(低效,10秒+出結(jié)果)
SELECT 學(xué)生姓名 FROM 學(xué)生表 WHERE 學(xué)生ID IN (SELECT 學(xué)生ID FROM 請假表);
結(jié)果:兩條SQL查詢結(jié)果一致,但EXISTS效率遠(yuǎn)超IN。
選擇建議:大數(shù)據(jù)量,不管結(jié)果是否一致,優(yōu)先用EXISTS。
五、必記口訣(不會混)
EXISTS:只問有沒有,找到就收手;不受NULL影響,大數(shù)據(jù)量優(yōu);
IN:要找全所有,再去做匹配;小數(shù)據(jù)量好用,有NULL就出錯。
(注:)