乍看之下,IN 和 EXISTS 的 SQL 語句似乎可以互換,但兩者在處理 NULL 值時行為差異巨大,甚至導致截然不同的結果。本文從 Oracle 數據庫的邏輯出發,剖析根本原因,並通過大量示例演示陷阱與解決之道。
在 Oracle 中,NULL 代表「未知」。任何與 NULL 的比較(=、!=、>、< 等)結果均為 UNKNOWN,而 WHERE 子句只接受 TRUE,會過濾掉 FALSE 和 UNKNOWN。只有 IS NULL / IS NOT NULL 能正確判斷。
-- 全部不返回行
SELECT 'true' FROM dual WHERE 1 = NULL;
SELECT 'true' FROM dual WHERE 1 != NULL;
-- 僅這兩條返回行
SELECT 'true' FROM dual WHERE 1 IS NULL;
SELECT 'true' FROM dual WHERE NULL IS NULL;
IN 實質等價於 = ANY,逐個比較子查詢返回的值。若列表中含 NULL,比較結果為 UNKNOWN,導致整個條件不成立:
-- 全部不返回行
SELECT 'true' FROM dual WHERE NULL IN (NULL);
SELECT 'true' FROM dual WHERE (NULL, NULL) IN ((NULL, NULL));
SELECT 'true' FROM dual WHERE (1, NULL) IN ((1, NULL));
-- 等價形式同樣失效
SELECT 'true' FROM dual WHERE NULL = ANY (NULL);
SELECT 'true' FROM dual WHERE (NULL, NULL) = ANY ((NULL, NULL));
EXISTS 只關心子查詢是否返回「行」,不關心具體列值。因此子查詢選 NULL 或常數均可,只要行存在即為 TRUE:
-- 均返回 'true'
SELECT 'true' FROM dual WHERE EXISTS (SELECT NULL FROM dual);
SELECT 'true' FROM dual WHERE EXISTS (SELECT 0 FROM dual WHERE NULL IS NULL);
當子查詢無 NULL 時,兩者結果一致:
SELECT ename FROM emp WHERE empno IN (SELECT mgr FROM emp);
SELECT ename FROM emp e WHERE EXISTS (SELECT 0 FROM emp WHERE mgr = e.empno);
NOT IN 等價於一連串 != AND != ...。若列表含 NULL,任一 != NULL 為 UNKNOWN,導致整個 WHERE 為 FALSE,查詢無任何結果:
-- 全部不返回行
SELECT 'true' FROM dual WHERE 1 NOT IN (NULL, 2);
SELECT 'true' FROM dual WHERE 1 != NULL AND 1 != 2;
SELECT 'true' FROM dual WHERE (1,2) NOT IN ((2,3),(2,NULL));
SELECT 'true' FROM dual WHERE (1,NULL) NOT IN ((1,2),(2,3));
NOT EXISTS 只要子查詢無行返回即為 TRUE,不受 NULL 干擾:
SELECT ename FROM emp WHERE empno NOT IN (SELECT mgr FROM emp); -- 可能為空
SELECT ename FROM emp e WHERE NOT EXISTS (SELECT 0 FROM emp WHERE mgr = e.empno); -- 正常
NOT IN,特別是子查詢可能返回 NULL 時。NOT IN,請在子查詢中過濾 NULL:SELECT ename FROM emp WHERE empno NOT IN (SELECT mgr FROM emp WHERE mgr IS NOT NULL);NVL/COALESCE 將 NULL 轉為不可能出現的值:SELECT ename FROM emp WHERE empno NOT IN (SELECT NVL(mgr, 0) FROM emp);徹底理解 NULL 的三值邏輯與 IN/EXISTS 的語義差異,能讓你寫出更健壯、結果正確的 SQL。