一段很绕的SQL(双重否定)
前段时间看到一段SQL,感觉实现的逻辑非常绕,而且看了之后想了很久都没有想明白到底是怎么回事,再一直想到我都能记住这段SQL的具体细节了,放到博客上研究一下。以下模拟环境:
create table t1 (a int,b int);
insert into t1 values(1,1);
insert into t1 values(1,2);
insert into t1 values(1,3);
insert into t1 values(2,2);
insert into t1 values(2,3);
insert into t1 values(4,1);
insert into t1 values(4,2);
insert into t1 values(4,3);
insert into t1 values(5,1);
insert into t1 values(5,6);
insert into t1 values(6,1);
commit;
SQL> select * from t1;
A B
----- -----
1 1
1 2
1 3
2 2
2 3
4 1
4 2
4 3
5 1
5 6
6 1
11 rows selected
问题是这样的:t1表中A代表人员编号,B代表事件编号。要求出t1表中所有包含人员2所对应的事件的人员编号。
它给出的答案是这样的:
select distinct a
from t1 a
where not exists (select 1
from t1 b
where a = '2'
and not exists (select 1
from t1 c
where c.a = a.a
and c.b = b.b));
首先可以确定的是结果肯定是正确的。然后来看一下逻辑:这段SQL中用了两层not exists,是用双重否定来取出最终结果的,但是具体的逻辑始终没有看明白,到网上搜索了一下,发现遇到这个问题的人还挺多,找了个比较能够看懂的解答看了下,总算明白了点:
关于这道题目可能需要这样来理解:
1、先看最内层
select *
from t1 b
where a = '2'
and not exists (select 1 from t1 c where c.b = b.b)
这个代表人员2对应的事件中,没有任何人对应的集合,很显然是一个空集,因为即便没有任何人对应,也有人员2自己对应。但是如果在c中加一个人员号的限定条件就不一定存在值了。
2、在最内层中加上a限定
select *
from t1 b
where a = '2'
and not exists (select 1
from t1 c
where c.a = &a
and c.b = b.b)
这就表示人员&a对应的事件中,没有和人员2对应事件相同的事件集合。所以只要在这个查询中没有值的,即表示这个人员对应的事件包含了所有人员2对应的事件。如果有值则表示人员2对应的事件有1件或1件以上是该人员所没有的。
3、最外层就比较明显了
select distinct a
from t1 a
where not exists (select 1
from t1 b
where a = '2'
and not exists (select 1
from t1 c
where c.a = a.a
and c.b = b.b))
用最外层的a.a代替了&a,即把所有人员都进行比对,凡事在下面不存在值的(刚才判断过了,不存在值即包含了2对应的所有事件),即需要获取的人员信息。