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可以用NVLNVL2或者COALESE函数处理NULL;SQL Server 可以使用 ISNULL函数; mysql可以使用IFNULLCOALESE函数)。容易出现和not in子句相似问题的还有 anyall 操作,例如 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 子句的“正确打开方式”的主要内容,如果未能解决你的问题,请参考以下文章

sql 循环 语句

在access利用SQL语句中如何创建表?

SQL 本身中的“提交”语句是事务吗?

有大神知道,sql server 中如何批量执行sql语句吗?

sql中常用sql语句

SQL(SQL/Oracle)使用序列从选择语句中插入值