有项目中客户提出sql的执行结果“错误”,没有按照预期得出结果,并且说很容易重现,大致就是说要找a表中有但是b表中没有的数据。最后发现是子查询中有null导致的,其实结果并没有错,只是使用方法进入误区了,在此记录一下这个很简单但是容易搞错的用法。
先构造一个简单的例子,创建两个表分表插入一条记录:
create table t1(c1 varchar(20));
insert into t1 values('a');
commit;
create table t2(c2 varchar(20));
insert into t2 values('b');
commit;
下面我们查询是否存在t1中但是t2中没有的数据,即t1相对于t2的差集。
select 1 from t1 where c1 not in(select c2 from t2);
select 1 from t1 where not exists (select 1 from t2 where t1.c1=t2.c2);
可以看到此时结果是符合预期的,此时在向t2中插入一个null值并查询
此时可以看到没有返回结果。
看似该结果并不是我们的预期结果,但实际上是进入了误区。由于t2表中插入了一条null值,那么我们知道达梦数据库中的null值表示“未知的值”,也就是说它有可能是任何值,也可能不是任何值,是一个很特殊的状态,这一点跟oracle是一致的。对于null值我们不能用普通的算术运算符去做比较,例如=null,这是不对的,null必须使用is或者is not进行比较。
那我们把上述例子等价改下一下相当于:
select 1 from dual where 'a'<>'b' and 'a'<>null;
其中’a’<>null永远为false,所以这个sql并不会返回结果。
那对于一些内表数据量很大的情况下,我们会改写成not exists的形式,我们来看一下结果是否符合预期:
select 1 from t1 where not exists (select 1 from t2 where t1.c1=t2.c2);
可以看到对于内表中有null的情况,not exists并不会影响到结果。
总结:
日常使用not in 进行子查询时,我们一定要注意子查询中是否有null,否则就会导致执行结果并不是自己预期的。为了安全起见,我们可以在子查询中过滤掉null或者使用not exists来规避内表的null问题。如下两种形式:
select 1 from t1 where c1 not in(select c2 from t2 where c2 is not null);
select 1 from t1 where not exists (select 1 from t2 where t1.c1=t2.c2);
文章
阅读量
获赞