SQL语句基本操作练习(五)

SQL语句基操(五)——依旧是查询

本篇文章记录了第八次作业
使用的数据库是SQL Server,使用的数据库管理软件是SQL Server Management Studio.
上一篇文章:SQL语句基本操作练习(四)
上次说到嵌套查询,剩了个EXISTS留在最后面。这是因为这一块涉及到离散数学中的谓词逻辑,有点绕,我希望能够放在这篇文章中仔细地再来讲讲。

(一)嵌套查询

4.带有EXISTS谓词的子查询

 EXISTS这个英文单词的意思是“存在”,在这里代表存在量词∃。在SQL中我们可以使用EXISTS和NOT EXISTS来进行逻辑判断。用EXISTS谓词的子查询返回的是true和false,而不是数据。内层查询为非空返回true,内层查询为空返回false,NOT EXISTS谓词与之相反。由EXISTS引出的内层查询的目标列表达式,我们一般都使用*,因为这里给出列名是没有实际意义的。

[例 3.60] 查询所有选修了1号课程的学生姓名
	 SELECT Sname
     FROM 	Student
     WHERE 	EXISTS
           (SELECT *
            FROM SC
            WHERE Sno=Student.Sno AND Cno= '1');

 很明显这是一个相关子查询,Student的值传入了子查询内。这里的结构逻辑是先从Student表中取出一个值,Sno,与SC表中的Sno值进行比对,若是二者相等且Cno为1,则会像父查询返回true表示存在这样一组数据,然后将其对应的Sname存入关系表中。如此反复直至Sno中所有值都被检验过。
 在这里我觉得EXISTS主要起了一个逻辑判断的作用,填补了之前运算符表达式的一种表达上的空缺。这一题也有别的查询语句可以实现,比如运用连接查询很简单就可以查到。

[例 3.61] 查询没有选修1号课程的学生姓名。

 厘清上一问后,这一题就显得很明了了,只是将EXISTS换成了NOT EXISTS。下面给出语句:

	SELECT 	Sname
    FROM 	Student
    WHERE 	NOT EXISTS
           (SELECT *
            FROM SC
            WHERE Sno=Student.Sno AND Cno= '1');

 注意:带有EXISTS和NOT EXISTS谓词的子查询可能不能被其他形式的子查询等价替换,但所有带有IN谓词、比较运算符、ANY和ALL谓词的子查询都可以用EXISTS谓词子查询来替换。也就是说EXISTS是它们的上位替代,但效率并不一定更高,这还是要看具体的使用情况。
 像之前的例3.55,对还是那个找同系学生的题,就可以用EXISTS来替换IN。

	SELECT Sno,Sname,Sdept	//例3.55改
    FROM Student S1
    WHERE EXISTS
         (SELECT *
          FROM Student S2
          WHERE S2.Sdept = S1.Sdept AND	//S1中找出和S2刘晨dept相同的
                S2.Sname = '刘晨');
//可以看到相比于原来的语句变得复杂了,所以这里其实没必要替换
[例 3.62] 查询选修了全部课程的学生姓名

 选修全部课程,这里的全部可以翻译成∀,但是SQL中是没有全称量词∀的。不过我们有存在量词。我们可以利用存在量词来婉转表达全称量词的含义。我们知道:
( ∀ x ) P ≡ ¬ ( ∃ x ( ¬ P ) ) (\forall x)P \equiv \lnot (\exists x(\lnot P)) (x)P¬(x(¬P))
这是个什么意思呢?看右边式子里面有两个否定,其实就是将全程量词变成了存在量词,双重否定表肯定嘛。那么带入到具体的语境中,比如这里的命题:

  • 选修了全部课程的学生

就可以转化为

  • 没有一门课程不选修的学生
    好活!这样一来我们就能使用EXISTS来写SQL语句啦!
	SELECT Sname
    FROM Student
	WHERE NOT EXISTS
       	     (SELECT *
              FROM Course
              WHERE NOT EXISTS
                   (SELECT *
                    FROM SC
                    WHERE Sno= Student.Sno
                    AND Cno= Course.Cno
                    )
             );

 那么这里内在的逻辑是怎样的呢?我们注意到这里选择使用的谓词是NOT EXISTS,先记着。先看最内层的语句可以看到这里先比对的是Sno,也就是学号。当学号比对成功后会继续比对Cno,也即是课程号。
 假设第一个比对的学号是001,选修了所有课,一共有3门课。那么在Course的查询中,每比对一个Cno都会向Course查询中返回一个false(使用的是NOT EXISTS),那么3门课就是3个false,即
F ∨ F ∨ F = F F\lor F \lor F = F FFF=F
意思就是返回给最外层的是一个F值,然后再把它NOT EXISTS一下就成了T值,即该学生选修了全部的课程。如果该学生有一门课没选修,上式中就会出现一个T值,最后的值肯定就是T了,那么在最外层就会变为F,意为该生没能选修全部课程。
 若是我们不使用NOT EXISTS而是EXISTS呢?那么这就和我们的命题不符了,只能说是存在而不能说是全部,这样最后的结果就是只要是选了一门课的都会跳出来。

[例 3.63] 查询至少选修了学生201215122选修的全部课程的学生号码

 对于这个查询,我们用逻辑蕴含来表达:查询学号为x的学生,对所有的课程y,只要201215122学生选修了课程y,则x也选修了y。

  • 用P表示谓词 “学生201215122选修了课程y

  • 用q表示谓词 “学生x选修了课程y
    则上述查询为: ( ∀ y ) p → q (\forall y)p \to q (y)pq
    p → q ≡ ¬ p ∨ q p \to q \equiv \lnot p \lor q pq¬pq
    ( ∀ y ) p → q ≡ ¬ ( ∃ y ( ¬ ( p → q ) ) ) ≡ ¬ ( ∃ y ( ¬ ( ¬ p ∨ q ) ) ) ≡ ¬ ∃ y ( p ∧ ¬ q ) (\forall y)p \to q \equiv \lnot (\exists y(\lnot (p \to q)))\equiv \lnot (\exists y(\lnot(\lnot p \lor q))) \equiv \lnot \exists y(p \land \lnot q) (y)pq¬(y(¬(pq)))¬(y(¬(¬pq)))¬y(p¬q)
    转化为语义是指:不存在这样的课程y,学生201215122选修了y,而学生x没有选。

	SELECT DISTINCT Sno
	FROM SC SCX
	WHERE NOT EXISTS
         (SELECT *
          FROM 	SC SCY
          WHERE SCY.Sno = '201215122'  AND
                NOT EXISTS
               (SELECT *
                FROM SC SCZ
                WHERE SCZ.Sno=SCX.Sno 
                AND   SCZ.Cno=SCY.Cno));

 因为这里用了三次SC表,我们给它们取SCX、SCY、SCZ三个别名。很明显这又是相关子查询,思路大体上和上一题一样,但更为复杂。SCX层是要进行查询比对的层,它传递了一个Sno进入到了最内层,SCY层作用是将课程号传入最内层。在SCZ这一层中,拿着上一层得到的课程号,与选到的课进行比对,若是选过这节课则返回true到上一层,否则返回false。
 与上题一样,我们假设被查询的学生把所有课都选过了一次,201215122选了3节课。那么该生在最内层都会返回T,到了第二层则会变成F,那么在第二层中要SELECT出符合条件的因为都是F,所以返回最外层的肯定是T(NOT EXISTS),最外层的T指的是没有201215122选的课该学生没有选
 若是该生选了201215122的课还选了别的课,而且有一些课没选,那么在第二层那些没选的课201215122肯定也没选,一个T(没选的课到第二层变成T)一个F(201215122没选)结果仍是F(这里是并关系,和上一题的第二层是不一样的),与刚刚的最终情况相同。
 若是该生选的课比201215122的课少,那么第二层会变成T,最后反应出来就是F。
 花了一些口舌说明了这个SQL语句是正确的,但是真要我写一个还是很难哈。

瞎bb

(二)集合查询

 集合查询主要有3种,并操作UNION,交操作INTERSECT和差操作EXCEPT。集合操作的查询结果是元组的集合,显示上较为直观。
注意:参加集合操作的各查询结果的列数必须相同,对应项的数据类型必须相同 。

[例 3.64] 查询计算机科学系的学生及年龄不大于19岁的学生。

 这是一个并集的操作,写出查询CS系和年龄不大于19的语句加上一个UNION就好了,没啥别的。

		SELECT *
        FROM Student
        WHERE Sdept= 'CS'
        UNION	/*若要保留重复元组请使用UNION ALL*/
        SELECT *
        FROM Student
        WHERE Sage<=19;

查询1

[例 3.65] 查询选修了课程1或者选修了课程2的学生。

 看到或者这个词就意味着我们可以选择并操作来完成这次查询。查询课程1再查询课程2,二者一并即可。

		SELECT Sno
        FROM SC
        WHERE Cno='1'
        UNION
        SELECT Sno
        FROM SC
        WHERE Cno= '2';

2

[例3.66] 查询计算机科学系的学生与年龄不大于19岁的学生的交集。

 题目既然已经点出来这是交集,那么直接上INTERSECT即可。

	SELECT *
	FROM Student
	WHERE Sdept='CS' 
	INTERSECT
	SELECT *
	FROM Student
	WHERE Sage<=19
	/*又或者可以使用单表查询*/
	SELECT *
	FROM Student
	WHERE Sdept='CS' AND Sage<=19;

3

[例 3.67] 查询既选修了课程1又选修了课程2的学生

 既···又···这个关联词表示这一题同样是个交集的操作。

	SELECT Sno
    FROM SC
    WHERE Cno='1' 
    INTERSECT
    SELECT Sno
    FROM SC
    WHERE Cno='2';
    /*亦或者选择嵌套查询*/
    SELECT Sno
    FROM SC
    WHERE Cno='1' AND Sno IN
    		(SELECT Sno
    		 FROM SC
    		 WHERE Cno='2');

4

[例 3.68] 查询计算机科学系的学生与年龄不大于19岁的学生的差集。

 题目点出来了这是一个差集,我们不需要多思考。这题求差集的意思就是要查询CS系中年龄大于19岁的学生。

	SELECT *
    FROM Student
    WHERE Sdept='CS'
    EXCEPT
    SELECT  *
    FROM Student
    WHERE Sage <=19;
    /*Another Version*/
    SELECT *
	FROM Student
	WHERE Sdept='CS' AND Sage>19;

5

(三)基于派生表的查询

 子查询不仅可以出现在WHERE子句中,
还可以出现在FROM子句中,这时子查询生成的临时派生表成为主查询的查询对象。
如例3.57和例3.60可以改写为以下形式:

	/*例3.57 找出每个学生超过他自己选修课程平均成绩的课程号*/
	SELECT Sno, Cno
    FROM SC, (SELECT  Sno, Avg(Grade) FROM SC GROUP BY  Sno)	/*此处将所有的平均成绩按Sno排列,被下面作为临时派生表的基础*/
              AS Avg_sc(avg_sno,avg_grade)	/*建立一个临时派生表Avg_sc,里面有avg_sno,avg_grade两个属性*/
    		  WHERE SC.Sno = Avg_sc.avg_sno   and SC.Grade >=Avg_sc.avg_grade
    /*例3.60 查询所有选修了1号课程的学生姓名*/
    SELECT Sname
    FROM   Student,  
    (SELECT Sno FROM SC WHERE Cno=' 1 ') AS SC1
     /*通过FROM子句生成派生表,AS关键字可以省略掉,但是你必须为派生关系指定别名*/
    WHERE  Student.Sno=SC1.Sno;

 临时派生表究竟是什么意思呢?看上面的例3.57重制版,第二行建立了一个储存平均成绩并排序的表,第三行就是将其声明为临时派生表,让它可以拿出来被使用。第四行就是一个普通的连接查询了。总的来说临时派生表就是将一组处理过的数据作为临时的表,使查询能够更方便地进行。

结语

 在学习查询语句的过程中,我感受最深刻的就是一个查询目的不一定只有一种语句对应。也就是说,很多语句都可以实现某一个操作,具体的选择则需要用户根据自己的习惯以及效率来确定。不同的人写出的语句风格不一样但最后的目的是相同的。
 再一个就是感受到了数学思想在计算机科学中的应用。尽管是SQL语句这种看起来和编程没有什么关系的应用,它也无时无刻不体现着数学思想:关系代数、逻辑谓词和交并差集等等。
FUUUBUKI!
下一篇文章:SQL语句基本操作练习(六)

 

 
参考文献:
[1]萨师煊,王珊,数据库系统概论.5版.北京:高等教育出版社,2014.
[2]David老师的PPT.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值