sql查询之双重not exists实现关系代数除运算
使用数据表
成绩信息表:
课程信息表:
问题:
-
查询选修了1,2号课程的学生学号
-
查询选修了全部课程的学生
1.
先上个代码:
/*1. 查询选修了1,2号课程的学生学号(因为这篇笔记主要是说明除运算的实现,其他方式解决这个问题的就略过了)*/
select distinct s1.学号
from 成绩信息 s1
where not exists(
select *
from tmp s2
where not exists(
select *
from 成绩信息 s3
where s3.学号 = s1.学号 and s3.课程序号 = s2.课程序号
)
)
-- tmp是一个视图,下面是创建语句
go
create view tmp
as
select 课程序号
from 课程信息
where 课程序号 in ('1', '2');
go
select * from tmp;
先来看看这个视图,得到的结果如下:
很明显,这只是为了创建一个只含有1,2课程号的表,当然可以通过其他方式创建,比如直接创建一个表,然后插入数据
接着看看这个查询,这里用到了两重not exists
,
第一重是对成绩信息表进行的扫描,select 可以列举成对表的每一个元组的枚举,对于not exists
可以理解成exists
的结果取反,即true变false, false变true, 还有即使别名s1是为了方便写起的
然后就是第二层not exists
,这个是对创建的那个视图的扫描。也就是在第一重扫描时,没扫描一个元组,就会进入第二层的扫描,即第一层每次执行一次,就会执行一次这个代码
select *
from tmp s2
.../*后面第二个not exists先忽略*/
扫描过程就是类似图中这样,类似于for循环
再将第二个扫描的not exists那部分加进来
select *
from tmp s2
where not exists(
select *
from 成绩信息 s3
where s3.学号 = s1.学号 and s3.课程序号 = s2.课程序号
)
加了条件之后对于tmp的扫描进行了一个筛选,
第二重not exists中的条件很好理解,就是拿s2(tmp视图)中的课程序号, s1 (成绩信息表)的学号 去s3中做一个筛选, 当s1中的学号和s2中的课程序号在s3表中找得到时, 第二重not exists中的条件就会返回的是true, 经过第二重的not exists之后就是false,那么对第二重扫描(也就是第二个select)扫描到的这个结果就会被抛弃,然后就是继续第二层扫描(select)。如果在第二层扫描中的tmp扫描完了,其中的not exists之后的每一个条件都是false,那么第二层扫描中tmp的所有的元组都被丢弃,也就是第二层扫描的结果为空,这个时候第一层扫描的条件经过not exists就会是true。因为exists对于他其中的条件得到的结果为空,那么exists之后就是false,再经过not之后就是true了。然后第一层继续往下,又重复上面这个过程。
这里补充贴个第二重not exists
中的sql语句执行图
这个条件就是一个自然连接。
下面就来详细说明这个语句执行的每一步
-
第一层扫描(即第一个select那), 拿着s1的第一个元组(1300310101,1,92),
然后到第一层的not exists那,进入第二层扫描,
第二层扫描扫描第一个(如下图), s1中的学号和s2中的课号作为条件在s3中可以找得到元组
那么第二重not exists返回的结果就是false(找得到exists返回true,not 取反), 也就是说第二层扫描得到得结果到现在为空,
第二层扫描还没有结束,继续,如下图,同样第二层扫描得条件执行后还是找得到结果,那么第二层的not exists条件返回为false,所以 第二层扫描到现在还是空
第二层扫描到现在已经结束了,因为s2(tmp表)到现在已经扫完了,并且第二层扫描的结果为空,第二层的扫描结果作为第一层的判断条件,为空经过not exists之后就是true了,也就是第一层扫描第一个元组时被留下来了(因为条件为true),那么到现在为止,s1的第一个元组就被保留到最终结果了。
-
现在就到了第一层扫描的第二个元组了,也就是(1300310101, 2, 95), 同样执行第一层的条件,进入到了第二层的扫描,和上边过程类似,贴个图应该能看得懂
进入第二层的扫描之后,执行第二层的条件:在s3中找。这个过程和第一层扫描第一个元组时一样,第二层扫描中的条件经过not exists之后返回的都是false,
那么第一层的条件经过not exists之后就是true了,s1表的第二个元组留了下来
-
现在到第一层扫描的第三个元组,(1300310101, 5, 88),同样执行第一层扫描的条件进入第二层扫描,过程
同样,第二层扫描中的每一个元组都被丢弃,(因为和上面一样,第二层扫描中的where条件经过not exists之后返回的都是false),那么第一层条件就是true,s1表第三个元组也被保存下来。
-
第一层扫描的第四个元组,过程
第一层扫描进入条件执行第二层扫描,第二层扫描又执行其条件在s3中查找,
第二层扫描到第一个元组s2中的课程序号为1的元组,带入执行条件,学号为1300330102,课号为1的元组找不到,也就是第二层扫描not exists之后返回true, 那么第二层扫描中的第一个元组留了下来。
其实这个时候已经可以知道第一层循环的not exists条件返回的是true了(dbms也不会再往下执行完整个第二层扫描),因为第一层not exists中的查询查找到了结果,exists返回的就是true,取反之后就是false , 也就是第一层扫描的第4个元组被丢弃了。
-
同理,第一层扫描的第五个元组(太懒了,这个过程就省略了),也被留了下来
-
第六个元组也被留了下来。那么总共留下了5个元组。
可以看看没有去重的结果:
去个重就可以得出符合的学号了。
过程讲完了,再来看看这个过程做了什么。
首先扫描成绩信息表(s1), 然后拿着s1的学号,
通过嵌套子查询去找tmp表(s2表,存放一号课程和2号课程),
然后又经过一个嵌套,拿着s1的学号,和1号课程,2号课程分别去成绩信息表(s3)中查,看看有没有这个学号是s1(成绩信息表)中的,课号是s2(tmp表)中的。如果第二层扫描能得到0个结果,也就是意味学号为s1的学生对于s2表中的课号都选了,因为第二重not exists中的查询都能返回结果,经过not exists之后就都是false了,然后第二层扫描(即对s2)的扫描的每一个元组都丢弃,也就是第二层扫描得到0个结果了。然后经过第一层的not exists之后就是true了,那么这个选了s2表中的所有课程的学生就被留了下来了。然后扫描完第一层就能选出所有学生中选修了s2中所有课程的学生了。
虽然感觉说的也不怎么明白,但大概就是这样了。也许你可以将问题这么理解:
查询成绩信息表中的学生中除去只选了1,2号课程中的一部分或者没选的,这样一来剩下的就是选了1,2号课的学生了。然后双重not exists就可以筛选掉那些只选一部分课程的学生了。
明白了上面这个,第二问就没什么难度了,只是将s2表换成包含所有课程的表而已。
这里只给出代码,不再重复说了,这里和上面不同的是s2直接通过子查询来构建,对于我这的表,这个查询是没有符合的结果的
select distinct s1.学号 from 成绩信息 s1 where not exists( select * from (select distinct 课程序号 from 课程信息) as s2(课程序号) where not exists( select * from 成绩信息 s3 where s1.学号 = s3.学号 and s2.课程序号 = s3.课程序号 ) )
渐渐的忘记标题,补充一下关系代数中的除运算
这里只给出一个例子,具体定义怎么下的我也忘了。。。
简单说就是拿着sc表中的非cno属性去看它所对应的所有cno属性,然后看看这些属性是否包含了C表中的所有,如果包含了,就把这个属性值留下来。也就是一种包含关系。
而上边的双重not exists就是实现了这么一个除法关系,我的理解这个也是实现了包含关系,
再补充一下这样写行不行?
select * from sc where exists (
select *
from C
where exist(
select *
from sc
where sc.cno = c.cno
)
)
之所以这样的思路是:
在C表中进行一次扫描,看看sc表中的cno是否包含了C表中的cno,如果包含了,就把这个元组留下。
但实际上这样是不行了,因为只要c表中的任何一个包含在了sc中,那么exists返回就是true,然后sc表中这个元组就被留下来了。
所以直接用肯定的写法行不通,那就来双重否定not exists了。
大概就这么多了,再写我就得吐了,这要是复习还看不明白就砸手机了。
收工。