本章我们来针对关系代数做一次习题课,以此来巩固和加深对关系代数的理解及掌握。
【例】设有一个学生-课程教学数据库,包含3个关系,分别是:
学生关系: S(S#,SNAME,SEX,AGE,DEPT)
课程关系: C(C#,CNAME,CPNO,CREDIT)
选修关系: SC(S#,C#,GRADE)
请用关系代数表达下列每个查询:
1. 检索所在系为计科系(‘CS’)的全体学生。
编写关系代数表达式的关键就是确定目标关系。目标关系的确定步骤如下:
(1)明确筛选条件所涉及的列,找出这些列所在的关系。
(2)明确需要投影的列,以及这些列所在的关系。
(3)第1步和第2步所确定的关系都是我们的目标关系。找出这些关系连接的方式。如果这些关系无法直接连接,则找出能够让这些关系实现间接连接的其它关系。
(4)第1步、第2步、第3步所找出的所有关系均是我们的目标关系。
解析:需要查询的学生信息均在学生关系中,筛选条件DEPT(系)也在学生关系中,所以本题的目标关系是学生关系(S)。需要再次强调的是:“CS”是一个字符串常量,所以在选择条件中一定要用单引号包围(‘CS’)。
σDEPT=‘cs’(S) 或者 σ5=‘cs’(S)
说明:这里通过列名或列序号均合法。但推荐使用列名的方式,更明确。
2. 检索年龄小于20的学生。
σAGE<20(S)
3. 检索所有学生的姓名和所在系。
πSNAME ,DEPT(S)
4. 检索选修了课程号C2的学生的学号与成绩。
πS#,GRADE(σC#=‘C2’(SC))
5. 检索选修了课程号C2的学生的学号与姓名。
解析:考虑到需要查询的列为学生的学号和姓名,而筛选条件是课程号,因此目标关系可以有两种组合:(学生关系,选课关系)或(学生关系,选课关系,课程关系)。之所以会出现这种情况是因为“课程号”在选课关系和课程关系中都有出现。但是前面我们谈到过连接运算的性能问题,结论是:如果可以不做连接,则坚决不做连接;如果可以少做连接,则坚决少做连接。因此确定目标关系为学生关系和选课关系,两个关系通过外键S#作等值比较从而实现自然连接。
πS#,SNAME(σC#=‘C2’(S⋈SC))
或者
πS#,SNAME(S⋈σC#=‘C2’(SC))
以上两种写法均可,但根据“连接”一节的讨论,第2种写法效率更更优。
6. 检索选修了课程名为MATHS的学生的学号与姓名。
解析:本例需要查询的学号与姓名均在学生关系中,课程名则在课程关系中,考虑到学生关系与课程关系之间无关联字段,无法直接连接,因此加入选课关系作为连接的桥梁。
πS#,SNAME(σCNAME=‘MATHS’(S⋈SC⋈C))
7. 检索选修了课程号为C2或C4的学生的学号。
解析:如果选择条件有多个,则可以依据条件之间的关系通过“与”(∧)或“或”(∨)来加以连接。
πS#(σC#=‘C2’ ∨C#=‘C4’(SC))
8. 检索至少选修了课程号为C2和C4的学生的学号。
方法一:为了便于大家理解,此例可变换为另一种问法:“检索将C2和C4课程全部选修的学生的学号”。需要说明的是:本例只关注学生是否全部选修了C2和C4,因此原题中的“至少”并不重要,除了C2和C4以外,学生还有没有选修其它课程,这并不是本例关注的。
为什么要这样变换问法呢?目的就是希望让大家看到“全部”二字,以便唤起大家对除法的记忆。没错,这道题就是用除法运算来解决的!因为我们的目标是判定每位学生对(C2,C4)这两门课程的选修情况,一定要注意,不是说C2和C4修一门即可,而是必须全部修完两门课程,所以根据前面对除法运算的讨论,(C2,C4)就是除法运算中的除数。(C2,C4)并不存在,因此需要使用关系代数表达式:Π c#(σc#=‘c2' ∨ c#=‘c4’(sc)) 来构造。前面我们说过,被除数一定包含除数所含有的列(C#),以及希望查询的列(S#),所以被除数就是从sc中投影出这两列:Πs#,c#(sc)。(关于S#和C#为什么是从SC中投影获取的原因,请参见第11题最后的强调说明。)在确定了被除数和除数以后,我们就可以写出本例第一种解法的关系代数表达式:
Πs#,c#(sc)÷ Π c#(σc#=‘c2' ∨ c#=‘c4’(sc))
方法二:另一种思路是使用集合的交运算;分别找出选修了C2号课程和C4号课程的学生,它们的交集就是同时选修了C2和C4号课程的学生。
Πs#((σc#=‘c2’ (sc)) ∩ Π s#(σc#=‘c4’(sc))
9. 检索至少选修了一门其先修课程为C5号课程的学生姓名。
解析:这道题需要转弯,即你需要首先找到哪些课程的先修课程是C5?这可以通过σCPNO=‘C5’ (C)来找到。在确定了目标课程以后,我们就可以拿着课程号去选修关系中寻找选修了这些课程的学生:Πs#(σCPNO=‘C5’ (C)⋈SC)。鉴于选修关系中存在的列,我们只能找到这些学生的学号(S#),然而我们需要的是学生的姓名,所以继续通过拿到的学号去学生关系中搜索他们的姓名:Πs#(σCPNO=‘C5’ (C)⋈SC)⋈ΠS#,SNAME (s)。最后我们将不需要的学号去掉,仅投影出题目中要求的姓名即可,最终答案如下:
ΠSNAME(Πs#(σCPNO=‘C5’ (C)⋈SC)⋈ΠS#,SNAME (s))
10. 检索没有选修课程号为C2的学生的姓名与年龄。
解析:此例采用集合运算的减法是比较巧妙的:用所有学生减去选修了C2课程的学生即为未选修C2课程的学生。
ΠSNAME,AGE(S) — ΠSNAME,AGE(σC#=‘C2’(S⋈SC))
11. 检索选修了全部课程的学生学号与姓名。
解析:OK!看到“全部”了!还用说吗:除法运算!!!除数是“全部课程”,或者准确的讲是全部课程的课程号(ΠC#(c)),课程的其它信息在此没有意义;被除数是“课程号”加上希望查询的“学号”(Π S#,C#(SC))。我们暂且不管学生的姓名,当我们找到目标学生的学号时,可以很简单地通过一个与学生关系的连接来找出他们的姓名。因此,除法运算的表达式为:Π S#,C#(SC)÷ΠC#(c)。最后与S连接来找出学生的姓名,最终答案如下:
Π S#,SNAME((Π S#,C#(SC)÷ΠC#(c))⋈S)
这里需要再次强调的是:在确定被除数时,我们说被除数包含“课程号”和“学号”。这两列的组合数据只能是从选课关系中获取,不能通过其它方式获得,比如将S中的S#和C中的C#做笛卡儿积。因为笛卡儿积的结果是毫无意义的,不能反映学生选课的真实情况。根据除法的运算性质,我们会将每一位学生的学号和每一门课程的课程号进行组合,然后将组合的数据拿到被除数中进行验证,以判断组合数据是否是真实有效的数据,所以必须保证被除数中的数据是学生真实的选课数据,而不是随便臆造的。
12. 检索所学课程包含学生S3所学课程的学生学号。
解析:不知道大家是否意识到:这道题暗含了一个词汇——“全部”或“所有”。因为题目可以解释为:检索那些将S3所学课程全部选修的学生的学号。所以有时候没有出现“全部”或“所有”或“都”等字眼的时候,是否可以用除法?也需要大家仔细理解题意,做出正确判断。既然明确了除法,那么需要我们做的便是确定“除数”和“被除数”。显然我们要找的人是选了了S3所学的全部课程,所以除数是“S3所学的全部课程”,用一个关系代数表达式来构造:ΠC#(σS#=‘S3’(SC)。被除数自然就是希望查询的列和除数包含的列所构成的一个组合,即(S#,C#)。我们使用关系代数表达式:ΠS#,C#(SC)从SC中取出所有学生的选课数据,从而构造出被除数。最终的答案如下:
ΠS#,C#(SC) ÷ ΠC#(σS#=‘S3’(SC))
再次说明:也许有的同学还是没有明白为什么用选课数据除以S3所学的全部课程就是目标学生?在此,我再解释一遍:
ΠS#,C#(SC) ÷ ΠC#(σS#=‘S3’(SC))的运算过程就是取出被除数中的S#集合,将其和除数中的每一个元祖进行笛卡儿积运算,然后通过判定笛卡儿积的全部结果是否都存在于被除数中,从而来确定商。
为讨论方便,便于大家理解,这里我们假定S#集合中存在学生S100;而且除数是(C5,C7,C22),即S3所选的课程是(C5,C7,C22)。好了,那么我们怎么判定S100是否是我们的商呢?方法就是将S100和除数进行笛卡儿积运算,产生结果{(S100,C5),(S100,C7),(S100,C22)}。这三条数据是什么?这就是我们臆想出来的S100对S3所学课程的选课数据。S3真的选过这些课吗?要判定这一点就必须去保存了真实选课数据的被除数中去验证它们是否真的存在?如果三条数据全部在被除数中被找到,那么就说明S100的的确确是选修了S3所学的全部课程;否则就只能说明S100未能全部选修S3所学课程。通过这样的过程,我们就对S100是否是商进行了判定。所以根据上述确定商的过程,反过来讲,如果S100是商的话,那么它一定选修了S3所学的全部课程。
至于其他学生,只不过是重复上述过程,逐一判断罢了,不再赘述。
好了,希望大家能真正理解。

