本文根据《数据库系统概论》(第6版)中的student,coutse,sc三张表作为数据源。并详细探讨EXISTS谓词作用,用法及原理。
student表:
course表:
sc表:
EXISTS代表存在量词 ∃ \exists ∃。带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值 t r u e / f a l s e true/false true/false,所以查询列名用*即可。
查询所有选修了81001号课程的学生姓名。
select sname from student where exists (select * from sc where sno=student.sno and cno='81001');
本查询中子查询条件依赖于外层父查询的某个属性值(student的sno值),因此也是相关子查询。处理过程如下:
首先取外层查询中 s t u d e n t student student表的第一个元组,根据它与内层查询相关的属性值(sno值)处理内层查询,若where子句返回值为真,则取外层查询中该元组的sname放入结果表,然后再取student表的下一个元组。重复这一过程,直到外层student表全部检查完为止。
例 1 : \Large 例1: 例1:查询选修了全部课程的学生姓名
SELECT sname FROM student WHERE NOT EXISTS (SELECT * FROM course WHERE NOT EXISTS (SELECT * FROM sc WHERE sc.sno=student.sno AND sc.cno=course.cno));
例 2 : \Large 例2: 例2:查询至少选修了学生20180002选修的全部课程的学生的学号(不存在这样的课程 y y y,学生 21080002 21080002 21080002选修了 y y y,而学生 x x x没有选修)
SELECT sno FROM student WHERE NOT EXISTS (SELECT * FROM sc WHERE sno='20180002' AND NOT EXISTS (SELECT * FROM sc X WHERE x.sno=student.sno AND x.cno=sc.cno));
EXISTS(包括 NOT EXISTS )子句的返回值是一个BOOL值。 EXISTS内部有一个子查询语句(SELECT … FROM…), 我将其称为EXIST的内查询语句。其内查询语句返回一个结果集。 EXISTS子句根据其内查询语句的结果集空或者非空,返回一个布尔值。
一种通俗的可以理解为:将外查询表的每一行,代入内查询作为检验,如果内查询返回的结果取非空值,则EXISTS子句返回TRUE,这一行行可作为外查询的结果行,否则不能作为结果。
SELECT *FROM student WHERE EXISTS(SELECT cno FROM course WHERE cno='81009')
上句因为 e x i s t s exists exists始终返回的是 f a l s e false false(没有81009这门课程),所以外层查询始终无效,也就不会产生数据。
分析器会先看语句的第一个词,当它发现第一个词是SELECT关键字的时候,它会跳到FROM关键字,然后通过FROM关键字找到表名并把表装入内存。并通过指针找到第一条记录,接着是找WHERE关键字计算它的条件表达式,如果找不到则返回到SELECT字段解析。如果找到WHERE,则分析其中的条件,如果为真那么把这条记录装到一个虚表当中,指针再指向下一条记录。如果为假那么指针直接指向下一条记录,而不进行其它操作。一直检索完整个表,并把检索出来的虚拟表返回给用户。完成后再回到SELECT分析字段。最后形成一张我们要的虚表。
WHERE关键字后面的是条件表达式。条件表达式计算完成后,会有一个返回值,即非0或0,非0即为真(true),0即为假(false)。同理EXISTS后面的条件也有一个返回值,真或假,来确定接下来执不执行SELECT。
EXISTS与IN的使用效率的问题,通常情况下采用exists要比in效率高,但要看实际情况具体使用:IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。
MySQL相关子查询执行顺序如下:
首先执行一次外部查询
对于外部查询中的每一行分别执行一次子查询,而且每次执行子查询时都会引用外部查询中当前行的值。
使用子查询的结果来确定外部查询的结果集。
==总结:==总的来说Exists执行的流程Exists首先执行外层查询,再执行内存查询,与IN相反。 流程为首先取出外层中的第一元组, 再执行内层查询,将外层表的第一元组代入,若内层查询为真,即有结果时。返回外层表中的第一元组,接着取出第二元组,执行相同的算法。一直到扫描完外层整表 。
再来看看:查询选修了全部课程的学生姓名
SELECT sname FROM student WHERE NOT EXISTS (SELECT * FROM course WHERE NOT EXISTS (SELECT * FROM sc WHERE sc.sno=student.sno AND sc.cno=course.cno));
全面分析这句话,首先在题目上,题目可以转换为:不存在一门课程这个学生没有选修。因为没有MySQL没有任意一个这样的谓词, 只能用EXISTS或者NOT EXISTS来表示。这也是EXISTS存在意义。
所以外层查询语句代表SELECT sname FROM student WHERE NOT EXISTS(一门课程这个学生没有选修) 接下来就是把course表中的课程依次拿出来找出没有选修的。因为NOT EXISTS子查询中找不到的提交结果集。NOT EXISTS查询的本质还是相关查询,所以只要把在最后一个select中sc.sno=student.sno AND sc.cno=course.cno满足这个就可以将这个同学选课信息通过sc表和crouse的课程连接一遍,找到连接不上的,即: 没有选修的。这样就找到了一门课这个学生没有选修,也即存在没有选修的课,那么该学生被去掉,进行下一个同学的判断 。
这样可以形成一个结构:
SELECT Sname
FROM Student
再从程序结构上解析这句话:EXISTS的相关子查询语句就是一个嵌套循环,内层循环对应外层循环。转化为C++代码结构如下:
for(i=1;ibool notExitst=true; //notExitst同上面NOT EXISTS for(i=j;j for(sc=1;sc if(sc.sno=student.sno AND sc.cno=course.cno){ //如果该学生这门课在sc表中找到对应,那么第二个NOT EXISTS为false不返回任何结果,第二层course进入下一个循环 notExitst=false; break; } } //如果sc表遍历完还没有找到,则第二个NOT EXISTS为true,返回该学生元组,但下面15行第一个NOT EXISTS取反就变为false if(notExitst) break; } //这是第一个NOT EXISTS,如果学生选课信息表sc都能对应课程号course.cno,即内层为notExitst=false,那么该学生信息放入结果集 if(!notExitst){将sname放入结果集;} //只有内层notExitst=false时才将sname放入结果集 }
从这里可以看出EXISTS语句效率取决于外层表元组大小,所以EXISTS适合外表小,内表大的查询。这是与IN在效率上本质区别。
同时也不难理解EXISTS只有两种情况会为上层传递返回值:①当EXISTS谓词判定为true时;②内层表所有元组遍历完时。
MySQL代码逻辑是这样的:
关于EXISTS与NOT EXISTS什么时候用:当题中涉及到,“全部”、"任意一个"这样的谓词时,应该转化为 ∃ \exists ∃谓词,使用EXISTS语句。一般来说NOT EXISTS(select *From 表2)修饰的谓词要遍历表2内所有元组,当所有元组遍历完后仍不满足条件,即为NULL时,NOT EXISTS才返回true。而EXISTS谓词只需要在表二中找到满足条件的元组,即可返回true。
更多内容请参考这里