相关推荐recommended
MySQL中EXISTS谓词作用及原理
作者:mmseoamin日期:2024-04-30

文章目录

  • 一. 带有 E X I S T S EXISTS EXISTS谓词的子查询
    • EXISTS谓词作用
    • 二. ==EXISTS原理==
      • 1. 书中定义
      • 2. 将相关子查询转换为C++语言
      • 3. MySQL相关子查询代码逻辑

        本文根据《数据库系统概论》(第6版)中的student,coutse,sc三张表作为数据源。并详细探讨EXISTS谓词作用,用法及原理。

        student表:

        MySQL中EXISTS谓词作用及原理,student表,第1张

        course表:

        MySQL中EXISTS谓词作用及原理,course表,第2张

        sc表:

        MySQL中EXISTS谓词作用及原理,sc表,第3张

        一. 带有 E X I S T S EXISTS EXISTS谓词的子查询

        EXISTS谓词作用

        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原理

        1. 书中定义

        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相关子查询执行顺序如下:

        1. 首先执行一次外部查询

        2. 对于外部查询中的每一行分别执行一次子查询,而且每次执行子查询时都会引用外部查询中当前行的值。

        3. 使用子查询的结果来确定外部查询的结果集。

        ==总结:==总的来说Exists执行的流程Exists首先执行外层查询,再执行内存查询,与IN相反。 流程为首先取出外层中的第一元组, 再执行内层查询,将外层表的第一元组代入,若内层查询为真,即有结果时。返回外层表中的第一元组,接着取出第二元组,执行相同的算法。一直到扫描完外层整表 。

        2. 将相关子查询转换为C++语言

        再来看看:查询选修了全部课程的学生姓名

        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的课程连接一遍,找到连接不上的,即: 没有选修的。这样就找到了一门课这个学生没有选修,也即存在没有选修的课,那么该学生被去掉,进行下一个同学的判断 。

        这样可以形成一个结构:

        1. 第一个select就是 你要选的就是学生

          SELECT Sname

          FROM Student

        2. 第二个 select 就是课程
        3. 第三个select就是学生和课程发生关系的表:SC选修表让他们连接起来

        再从程序结构上解析这句话:EXISTS的相关子查询语句就是一个嵌套循环,内层循环对应外层循环。转化为C++代码结构如下:

        for(i=1;i
            bool 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时;②内层表所有元组遍历完时。

        3. MySQL相关子查询代码逻辑

        MySQL代码逻辑是这样的:

        1. 先将student表放入内存中并将指针指向第一个元组,即学号为 20180001 20180001 20180001学生;course表也是一样操作指针指向第一个元组 81001 81001 81001课程号,再将最内层sc表通过sc.sno=student.sno AND sc.cno=course.cno条件连接起来。
        2. 当sc表中找到满足(and谓词)学号是 20180001 20180001 20180001课程号是 81001 81001 81001元组时,内层NOT EXISTS不为NULL,返回false,即第二层course表不返回任何数据给第一层的student,而是继续将course表指针指向下一个元组81002课程号。重复此操作,直到当course中指针指向最后一个课程号,此时由于是最后一次循环,必定有返回值,如果为false则外层取反即该学生选修所有课程,将该学生保存至结果集中。最外层student指向第二个元组,即学号是 20180002 20180002 20180002学生。
        3. 重复上述操作,如果当course中指针指向一个元组的课程号,而学号和该课程号没有在sc中找到对应记录时,证明该学生没有学习这门课程,此时第二层course的NOT EXISTS返回true,将该学生信息传递给第一层的NOT EXISTS,而外层取反变为false,该记录值对应学号的学生不被保留。
        4. student表继续指向下一个学生号,重复上述三步操作。

        关于EXISTS与NOT EXISTS什么时候用:当题中涉及到,“全部”、"任意一个"这样的谓词时,应该转化为 ∃ \exists ∃谓词,使用EXISTS语句。一般来说NOT EXISTS(select *From 表2)修饰的谓词要遍历表2内所有元组,当所有元组遍历完后仍不满足条件,即为NULL时,NOT EXISTS才返回true。而EXISTS谓词只需要在表二中找到满足条件的元组,即可返回true。

        更多内容请参考这里