SQL语句中 NOT IN 子句的“正确打开方式”
Posted Code Explorer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL语句中 NOT IN 子句的“正确打开方式”相关的知识,希望对你有一定的参考价值。
在写SQL语句的时候,若where条件是判断用户不在某个集合当中,我们习惯使用 where 列名 not in (集合) 子句,然而,当集合中存在空值时,返回的结果永远为空。如下所示:
Oracle 或 MySQL
select 1 from dual where 1 not in(2, null)
返回0条结果。
SQL Server
select 1 where 1 not in(2, null)
返回0条结果。
显然在上面例子中的结果并非我们希望得到的结果。对于数据库管理系统
select 1 from dual where 1 not in(2, null)
会被转化为
select 1 from dual where 1 != 2 and 1 != null
我们看转化之后的SQL语句, 1 != null 的结果是不确定,所以where子句的结果不为true,因此返回0条记录。在写SQL代码的时候,有时候 where 列名 not in (集合) 子句中的集合可能是一个子查询,若子查询反馈的结果集中包含值为null的记录时,则不会有任何结果返回,这是SQL初学者很容易犯且很难被发现的一个错误。所以尽量使用其它形式的SQL语句替换not in子句,或者对子查询中的NULL进行处理(例如Oracle可以用NVL,NVL2或者COALESE函数处理NULL;SQL Server 可以使用 ISNULL函数; mysql可以使用IFNULL 或 COALESE函数)。容易出现和not in子句相似问题的还有 any 和 all 操作,例如 where 列名 != all (集合) 和上面的子句效果一样,这里不一一赘述。
下面通过一个具体实验来进行进一步的演示,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 | 2015-08-03 | 002 |
170107 | Rex | 2015-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 != all( select ISNULL(DEPARTMENT_ID,‘‘) from EMPLOYEES where HIRE_DATE like ‘%2015%‘ )
SQL Server DBMS将返回给我们期望的结果:
DEPARTMENT_ID DEPARTMENT_NAME 001 Administration
以上是关于SQL语句中 NOT IN 子句的“正确打开方式”的主要内容,如果未能解决你的问题,请参考以下文章