关于谓词查询的深入分析(select …where (not) exists (…))

来源:互联网 发布:一卡通食堂软件系统 编辑:程序博客网 时间:2024/06/04 20:12

DB中有三个表,如图:

Student表:

clip_image002

Course表:

clip_image004

SC表:(选课表)

clip_image006

首先看一个相对简单的查询:

题目1:查询学生200215122未选择的所有课程号。

我们的解决思路如下:

1、 对course表,取出第1行的cno

2、 (这一步由子查询做)然后与sc表的每行逐一对比,看是那些使得sno=’200215122’的行的cno是否等于从course取出的cno,若相等,则将这一

cno放入结果表1中,从而结果表1不为空,not exits返回假,那么从第1行取出的这个cno则不放入最终结果表中。若不相等,则结果表1为空,从而not exists为真,从第1行取出的这个cno则放入最终结果表中。

3、 继续1,2步,考察course中剩余的行,直到将course表中所有行,即所有课程考察完。

SELECT cno FROM course

WHERE NOT EXISTS

(

SELECT cno FROM sc

WHERE sno='200215122' ANDcno=course.cno

)

注意:将not exists 换成 cno not in也可(不这样具体的执行过程就不太一样了),另外对于存在谓词子查询而言,选择哪一列是无关紧要的,本例中子查询我们写的是“select cno from sc”,其实写成“select * from sc”亦可,前面的not exists的真假值只与结果表1是否为空有关。还要注意,在考察course表时,每行都会在子查询过程中生成自己的结果表1,该表只是外面not exists判断每个cno是否放到最终结果表的依据。

另外要注意上述的子查询是相关子查询,因为子查询的条件依赖于父查询,即子查询用来判断cno=course.cno的左边的cno由父查询传递而来,下面的伪码能清晰的表明这个意思。

将每个表都看成数组,将表中的每行看成一个对象,用伪代码表达上述查询的执行过程:

伪码1:

ResultTable rt;

for(int i=0;i<course.length;++i)

{

TempResultTable trt;

for(int j=0;j<sc.length;++j)

{

if(sc[j].sno='200215122' &&

course[i].cno==sc[j].cno)

{

trt.add(sc[j].cno);

/*每门课只能被一个学生选一次,这里应退出循环*/

break;

}

}

//若空,说明course[i].cno这门课没被200215122选上

//而没被选择上的课当放入最终的结果表rt中

if(trt.isEmpty())

{

rt.add(course[i].cno);

}

}

题目2:查询学生200215122选择的所有课程。

参考上面,不难写出相应的sql语句:

SELECT cno FROM course

WHERE EXISTS

(

SELECT cno FROM sc

WHERE sno='200215122' AND cno=course.cno

)

/*这是更为简单的查询方式,因为所有被选中的课程

的课程号都在sc表中,而之所以题目1使用的方式较为复杂,是因为当我们考察未被选中的课程的时候,必须要考察course表,因为未被选中的课程在该表中才能找到,毕竟该表保存所有课程的记录*/

select cno from sc

where sno='200215122'

 

参考伪码1不难分析出题目2第一种查询方式的伪码。

题目3:查询没有选课的学生。

这样的学生符合:用其学号考察选课表试图得到其选课表时,发现该选课表为空。

select sno from s where notexists

(

select cno from sc

where sno=sc.sno

)

其实就是选出所有这样的学号,使得子查询为空。其具体执行过程可以参照伪码1。

题目4:查询没有选择全部课程的学生。


SELECT cno FROM course

WHERE NOT EXISTS

(

SELECT cno FROM sc

WHERE sno='200215122' ANDcno=course.cno

)

左边是题目1的sql语句,我们知道,其返回的是200215122未选择的课程号集合,如果该集合不为空,我们就知道200215122便是没有选择全部课程的学生之一,这是对200215122的考察,我们要考察全体学生,就不能以一个具体的学号来考察,学生表中的学号当作为变量传递给子查询中(外层for循环传递给内层for循环)。

SELECT sno FROM student

WHERE EXISTS

(

SELECT cno FROM course

WHERE NOT EXISTS

(

SELECT cno FROM sc

WHERE sno=student.sno

AND cno=course.cno

)

)

具体分析:左边sql执行过程是这样的

1、外层for循环开始,从student表中取出第1个学号sno1

2、进入次外层for循环,从course表中取出第一个课号cno1

3、内层for循环用sno1和cno1去匹配sc表中的每行,若匹配上,则sno1选择了cno1,回到次外层循环,考察sno1是否选择了cno2,依次类推,直到将所有course表中的cno考察完。在为sno1考察course中的cno时,若在内层循环中,没有与sc中的任一行匹配上,则说明该生sno1没有选择该课程cno。那么将该生cno1放入最终结果表中。

4、重复上面的步骤考察sno2……直到学生表的所有学号都被考察过。

可见,上面的执行过程本质是三层循环。毕竟计算机要看某门课程是否被某学生选中或着不选中,它只能(sno,cno)去选择表中一一匹配,别无它法——事实上,让人来完成这件事情不也是这样做么。而exists和not exist的区别仅仅在于:若子查询生成的集合为空,前者返回假,后者返回真,反之类同。在考察父表中某一行时,如果谓词返回真,说明父查询在执行过程中的某一行,符合条件,该行便会放到最终结果表(集合)中。

题目5:查询选择了全部课程的学生。

有了上面的基础,这个就不是很难写了:

SELECT sno FROM student

WHERE NOT EXISTS

(

SELECT cno FROM course

WHERE NOT EXISTS

(

SELECT cno FROM sc

WHERE sno=student.sno

AND cno=course.cno

)

)

如果没有计算机,我们得自己动手来完成这个功能,我们会这样做:因为要考察所有学生,所以需要student表,又因为要考察所有课程所以需要课程表,又因为要考察选课情况,所以需要选课表。现在三个表都有了,开始考察吧。从student表中取出sno1,从course表中取出cno1,看看(sno1,cno1)是否在sc中……

0 0
原创粉丝点击