在寫SQL語句的時候,若where條件是判斷用戶不在某個集合當中,我們習慣使用 where 列名 not in (集合) 子句,這種寫法本身沒有問題,但實踐過程中卻發現很多人在寫類似的SQL語句時,寫的代碼存在隱患,而這種隱患往往難以發現。
1. 存在隱患的寫法
首先,我們來評估一條簡單的SQL語句的輸出結果。語句如下:
select 1 from dual where 1 not in(2, null)
簡單,輸出結果是1嘛。
錯!答案是沒有輸出結果。
為什么會這樣?數據庫管理系統在執行查詢之前,會對上面語句進行簡單的轉化,轉化之后的語句如下:
select 1 from dual where 1 != 2 and 1 != null
顯然,where后面的表達式中,1!=2的結果是true,但1!=null的結果是不確定,true 和 不確定進行與運算的結果非true,所以上述語句的沒有輸出結果。
分析完了上面這條語句,我們再來看一個問題,假設有員工表DEMPLOYEES和部門表DEPARTMENTS,寫SQL語句找出2015年沒有招人的部門。我們可以很快的寫出下面語句:
select DEPARTMENT_ID, DEPARTMENT_NAME from DEPARTMENTS where DEPARTMENT_ID not in( select DEPARTMENT_ID from EMPLOYEES where HIRE_DATE like '%2015%' )
若員工表EMPLOYEES中存在還未分配部門的員工時,子查詢中的結果集中會含NULL元素,這就和開始我們評估的語句套上了,語句將無結果返回。那么,該如何寫這種形式的語句才能避免隱患呢?
2. NOT IN 子句正確的打開方式
為了避免NOT IN子句的隱患,最簡單的方式就是不使用NOT IN子句,而使用其他的形式達到相同的效果。你老是出問題,我不跟你玩行了吧!例如使用 not exists子句將上述語句改寫正確之后代碼如下:
select DEPARTMENT_ID, DEPARTMENT_NAME from DEPARTMENTS where not exists ( select * from EMPLOYEES where EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID and HIRE_DATE like '%2015%' )
若還是想用 NOT IN子句,怎么辦?有方法,就是對子查詢返回的結果進行處理,如果為空值NULL,則給它賦一個非空值。這種對空值NULL的處理,各類數據庫管理系統都有相應函數函數支持,例如SQL Server中的 ISNULL函數,Oracle 中的NVL,NVL2,COALESE,以及MySQL中的IFNULL , COALESE。
以SQL Server為例,如下SQL語句也能夠像NOT EXISTS子句一樣避免隱患。
select DEPARTMENT_ID, DEPARTMENT_NAME from DEPARTMENTS where DEPARTMENT_ID != all( select ISNULL(DEPARTMENT_ID,'') from EMPLOYEES where HIRE_DATE like '%2015%' )
3. 小結
在使用NOT IN子句時,若子查詢結果集中可能包含空值NULL,則代碼存在隱患,要消除這種隱患,應該對子查詢結果集中的空值進行處理。
附:基於SQL Server的完整實驗代碼
假設公司ERP系統中有兩個表,員工表EMPLOYEES和部門表DEPARTMENTS(如下所示),老板要找出2015年沒有招人的部門號和部門名稱。請寫出查詢的SQL語句。
員工表 EMPLOYEES
EMPLOYEE_ID | EMPLOYEE_NAME | HIRE_DATE | DEPARTMENT_ID |
170101 | Bob | 2016-02-02 | 001 |
170102 | Alice | 2015-02-05 | 003 |
170103 | Tony | 2016-03-04 | 002 |
170105 | Aaron | 2016-08-03 | 002 |
170107 | Rex | 2016-10-11 | NULL |
部門表 DEPARTMENTS
DEPARTMENT_ID | DEPARTMENT_NAME | MANAGER_ID |
001 | Administration | 170101 |
002 | IT | 170103 |
003 | Finance | 170102 |
創建表SQL語句。

CREATE TABLE DEPARTMENTS( DEPARTMENT_ID CHAR(3) PRIMARY KEY, DEPARTMENT_NAME VARCHAR(100), MANAGER_ID CHAR(6) ); CREATE TABLE EMPLOYEES( EMPLOYEE_ID CHAR(6) PRIMARY KEY, EMPLOYEE_NAME VARCHAR(30) NOT NULL, HIRE_DATE DATE, DEPARTMENT_ID CHAR(3), FOREIGN KEY(DEPARTMENT_ID) REFERENCES DEPARTMENTS(DEPARTMENT_ID) ); insert into DEPARTMENTS values('001','Administration','170101'); insert into DEPARTMENTS values('002','IT','170103'); insert into DEPARTMENTS values('003','Finance','170102'); insert into EMPLOYEES values('170101', 'Bob', '2016-03-02', '001'); insert into EMPLOYEES values('170102', 'Alice', '2015-02-05', '003'); insert into EMPLOYEES values('170103', 'Tony', '2016-03-04', '002'); insert into EMPLOYEES values('170105', 'Aaron', '2016-08-03', '002'); insert into EMPLOYEES values('170107', 'Rex', '2016-10-11', NULL);
上述題目,我們很容易產生這樣的思路,先在員工表 EMPLOYEES 中找出2015年雇佣的員工所在的部門號,作為一個部門號子集合,然后在部門表 DEPARTMENTS 中找出不在該子集中的部門號和部門ID,即為要查找的結果。於是有了如下查詢SQL語句:
select DEPARTMENT_ID, DEPARTMENT_NAME from DEPARTMENTS where DEPARTMENT_ID not in( select DEPARTMENT_ID from EMPLOYEES where HIRE_DATE like '%2015%' )
然而,由於子查詢中存在一個空值,所以SQL Server數據庫管理系統執行上述語句之后將返回0條結果。而實際上我們可以從上表中看到,2015年沒有招人的部門號和部門名稱有{001, Administration } 部門,故上面的查詢語句存在BUG。
為保證查詢出我們期望的結果,這里使用SQL Server的ISNULL函數對子查詢中的空值進行處理,處理之后SQL語句為:
select DEPARTMENT_ID, DEPARTMENT_NAME from DEPARTMENTS where DEPARTMENT_ID not in( select ISNULL(DEPARTMENT_ID,'') from EMPLOYEES where HIRE_DATE like '%2015%' )
SQL Server DBMS將返回給我們期望的結果:
DEPARTMENT_ID DEPARTMENT_NAME 001 Administration 002 IT