跳转到内容

闭卷专题一:关系代数

关系代数是闭卷考核内容之一,对应教材第2章的“关系代数”部分。教材指出:关系代数是一种抽象的查询语言,它以关系作为运算对象,以关系作为运算结果,通过运算表达查询需求。

这部分最常见的题型不是背概念,而是:

给出若干关系模式和一个查询要求,要求写出关系代数表达式。

所以复习重点是:认识运算符、确定操作顺序、能把中文查询翻译成表达式


1. 预备知识:关系、元组与属性

Section titled “1. 预备知识:关系、元组与属性”

可以把关系理解为一张二维表:

数据库术语表格中的直观含义示例
关系一张表Student\mathrm{Student}
元组一行记录某个学生的一行信息
属性一列Sno\mathrm{Sno}Sname\mathrm{Sname}
列的取值范围学号字符串、成绩数值
候选码能唯一标识元组的最小属性组Sno\mathrm{Sno}

后面的例题统一使用教材中常见的学生选课关系:

Student(Sno, Sname, Ssex, Smajor)Course(Cno, Cname, Credit)SC(Sno, Cno, Grade)\begin{aligned} \mathrm{Student}(\mathrm{Sno},\ \mathrm{Sname},\ \mathrm{Ssex},\ \mathrm{Smajor}) \\ \mathrm{Course}(\mathrm{Cno},\ \mathrm{Cname},\ \mathrm{Credit}) \\ \mathrm{SC}(\mathrm{Sno},\ \mathrm{Cno},\ \mathrm{Grade}) \end{aligned}

其中,Student\mathrm{Student} 表示学生,Course\mathrm{Course} 表示课程,SC\mathrm{SC} 表示选课联系:一个学生可以选多门课,一门课可以被多名学生选择。


运算表达式用途直观理解
RSR \cup S合并属于 RRSS 的元组两张同结构表合并去重
RSR - S取属于 RR 但不属于 SS 的元组RR 中排除 SS
RSR \cap S取同时属于 RRSS 的元组两表共同部分
笛卡尔积R×SR \times S任意组合两关系中的元组每行和另一表每行配对
选择σF(R)\sigma_F(R)按条件 FF 筛选行SQL 中的 WHERE
投影πA1,A2,,An(R)\pi_{A_1,A_2,\ldots,A_n}(R)选取指定属性并去重SQL 中的列选择
条件连接RFSR \bowtie_F S按条件 FF 组合元组SQL 中的 JOIN ... ON
自然连接RSR \bowtie S按同名属性相等连接并去重列常用于有关联属性的表
R÷SR \div S表达满足 SS 中全部取值查询选修了全部课程的学生

必须特别注意:

并、差、交要求参与运算的关系具有相同或相容的属性结构。选择作用于行,投影作用于列。连接不是简单地任意组合两表数据,而是在组合结果上保留符合关联条件的元组。


选择是从关系中挑出满足条件的元组,也就是筛选行。形式为:

σF(R)\sigma_F(R)

其中,RR 为被操作的关系,FF 为选择条件。

查询计算机科学与技术专业的学生:

σSmajor=’计算机科学与技术’(Student)\sigma_{\mathrm{Smajor} = \text{'计算机科学与技术'}}(\mathrm{Student})

查询成绩不低于 9090 分的选课记录:

σGrade90(SC)\sigma_{\mathrm{Grade} \ge 90}(\mathrm{SC})

查询计算机科学与技术专业的女生:

σSmajor=’计算机科学与技术’Ssex=’女’(Student)\sigma_{\mathrm{Smajor} = \text{'计算机科学与技术'} \land \mathrm{Ssex} = \text{'女'}} (\mathrm{Student})

下面写法是错误的,因为它把筛选条件写在了投影运算中:

πSmajor=’计算机科学与技术’(Student)\pi_{\mathrm{Smajor}=\text{'计算机科学与技术'}}(\mathrm{Student})

应记住:σ\sigma 后写筛选条件,π\pi 后写要保留的属性列。


投影从关系中选择需要显示的属性列。形式为:

πA1,A2,,An(R)\pi_{A_1,A_2,\ldots,A_n}(R)

查询所有学生的学号和姓名:

πSno,Sname(Student)\pi_{\mathrm{Sno},\mathrm{Sname}}(\mathrm{Student})

查询所有被选择过的课程号:

πCno(SC)\pi_{\mathrm{Cno}}(\mathrm{SC})

因为关系不允许重复元组,如果很多学生选了同一门课,投影后的课程号结果中相同课程只保留一次。

查询计算机科学与技术专业学生的学号和姓名:

πSno,Sname(σSmajor=’计算机科学与技术’(Student))\pi_{\mathrm{Sno},\mathrm{Sname}} \left( \sigma_{\mathrm{Smajor}=\text{'计算机科学与技术'}} (\mathrm{Student}) \right)

运算顺序为:先通过 σ\sigma 筛出符合专业条件的学生,再通过 π\pi 仅显示学号与姓名。


集合运算的两个关系必须相容,通常要求属性个数相同,并且对应属性来自相同或可比较的域。

例如,可以把两个只包含属性 Sno\mathrm{Sno} 的查询结果进行集合运算,但不能直接将 Student\mathrm{Student}Course\mathrm{Course} 做并运算。

查询选修课程 C01\mathrm{C01}C02\mathrm{C02} 的学生学号:

πSno(σCno=’C01’(SC))  πSno(σCno=’C02’(SC))\pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC})\right) \ \cup\ \pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C02'}}(\mathrm{SC})\right)

查询同时选修课程 C01\mathrm{C01}C02\mathrm{C02} 的学生学号:

πSno(σCno=’C01’(SC))  πSno(σCno=’C02’(SC))\pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC})\right) \ \cap\ \pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C02'}}(\mathrm{SC})\right)

查询选修了 C01\mathrm{C01} 但没有选修 C02\mathrm{C02} 的学生学号:

πSno(σCno=’C01’(SC))πSno(σCno=’C02’(SC))\pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC})\right) - \pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C02'}}(\mathrm{SC})\right)

如果关系 Student\mathrm{Student}55 行,关系 SC\mathrm{SC}2020 行,则:

Student×SC\mathrm{Student} \times \mathrm{SC}

产生的元组数量为:

5×20=1005 \times 20 = 100

其中很多组合并没有实际业务意义。因此实际查询通常要在笛卡尔积基础上增加连接条件。

查询学生姓名及其选课成绩,需要将学生和选课记录按照相同学号连接:

πStudent.Sno,Sname,Cno,Grade(StudentStudent.Sno=SC.SnoSC)\pi_{\mathrm{Student.Sno},\mathrm{Sname},\mathrm{Cno},\mathrm{Grade}} \left( \mathrm{Student} \bowtie_{\mathrm{Student.Sno}=\mathrm{SC.Sno}} \mathrm{SC} \right)

这相当于 SQL 中的:

SELECT Student.Sno, Sname, Cno, Grade
FROM Student
JOIN SC ON Student.Sno = SC.Sno;

等值连接是连接条件使用等号的连接。例如:

StudentStudent.Sno=SC.SnoSC\mathrm{Student} \bowtie_{\mathrm{Student.Sno}=\mathrm{SC.Sno}} \mathrm{SC}

自然连接会在两个关系的同名且可比较属性上进行相等连接,并去掉结果中重复出现的同名连接属性。

Student\mathrm{Student}SC\mathrm{SC} 的同名联系属性只有 Sno\mathrm{Sno},则可以简写为:

StudentSC\mathrm{Student} \bowtie \mathrm{SC}

考试中如果同名属性含义不够明确,写出连接条件通常更稳妥。


题目:查询“选修了课程名称为数据库系统的学生学号和姓名”。

需要使用三个关系:Student\mathrm{Student} 提供姓名,SC\mathrm{SC} 表示学生选了哪门课程,Course\mathrm{Course} 提供课程名称。

若使用自然连接表达,可以写为:

πSno,Sname(StudentSCσCname=’数据库系统’(Course))\pi_{\mathrm{Sno},\mathrm{Sname}} \left( \mathrm{Student} \bowtie \mathrm{SC} \bowtie \sigma_{\mathrm{Cname}=\text{'数据库系统'}}(\mathrm{Course}) \right)

如果需要显式写连接条件,则可写为:

πStudent.Sno,Sname((StudentStudent.Sno=SC.SnoSC)SC.Cno=Course.CnoσCname=’数据库系统’(Course))\pi_{\mathrm{Student.Sno},\mathrm{Sname}} \left( \left( \mathrm{Student} \bowtie_{\mathrm{Student.Sno}=\mathrm{SC.Sno}} \mathrm{SC} \right) \bowtie_{\mathrm{SC.Cno}=\mathrm{Course.Cno}} \sigma_{\mathrm{Cname}=\text{'数据库系统'}}(\mathrm{Course}) \right)

解题顺序是:先找出课程名称对应的课程,再通过 SC\mathrm{SC} 找到选修者,最后通过 Student\mathrm{Student} 显示学生姓名,并用投影只保留题目要求的列。


8. 除运算 ÷\div:处理“全部”问题

Section titled “8. 除运算 ÷\div÷:处理“全部”问题”

题目中出现“全部”“所有”“每一门”“至少包含指定集合中的每一个”等表述时,应首先想到除运算。

一般形式是:

R(X,Y)÷S(Y)R(X,Y) \div S(Y)

其结果保留满足下列条件的 XX

yS,(x,y)R\forall y \in S,\quad (x,y) \in R

也就是:对 SS 中的每一个对象,xx 都与之存在匹配关系。

8.2 示例:查询选修了全部课程的学生学号

Section titled “8.2 示例:查询选修了全部课程的学生学号”

在关系 SC(Sno,Cno,Grade)\mathrm{SC}(\mathrm{Sno},\mathrm{Cno},\mathrm{Grade}) 中,先保留学生号与课程号:

πSno,Cno(SC)\pi_{\mathrm{Sno},\mathrm{Cno}}(\mathrm{SC})

全部课程号集合为:

πCno(Course)\pi_{\mathrm{Cno}}(\mathrm{Course})

因此表达式为:

πSno,Cno(SC)÷πCno(Course)\pi_{\mathrm{Sno},\mathrm{Cno}}(\mathrm{SC}) \div \pi_{\mathrm{Cno}}(\mathrm{Course})

结果为满足“对全部课程号都存在选课记录”的学生学号。

8.3 示例:查询选修了学生 S01\mathrm{S01} 所选全部课程的学生

Section titled “8.3 示例:查询选修了学生 S01\mathrm{S01}S01 所选全部课程的学生”

指定学生 S01\mathrm{S01} 所选课程集合为:

πCno(σSno=’S01’(SC))\pi_{\mathrm{Cno}} \left( \sigma_{\mathrm{Sno}=\text{'S01'}}(\mathrm{SC}) \right)

所有学生选课关系除以该课程集合:

πSno,Cno(SC)÷πCno(σSno=’S01’(SC))\pi_{\mathrm{Sno},\mathrm{Cno}}(\mathrm{SC}) \div \pi_{\mathrm{Cno}} \left( \sigma_{\mathrm{Sno}=\text{'S01'}}(\mathrm{SC}) \right)

仍使用以下三个关系:

Student(Sno, Sname, Ssex, Smajor)Course(Cno, Cname, Credit)SC(Sno, Cno, Grade)\begin{aligned} \mathrm{Student}(\mathrm{Sno},\ \mathrm{Sname},\ \mathrm{Ssex},\ \mathrm{Smajor}) \\ \mathrm{Course}(\mathrm{Cno},\ \mathrm{Cname},\ \mathrm{Credit}) \\ \mathrm{SC}(\mathrm{Sno},\ \mathrm{Cno},\ \mathrm{Grade}) \end{aligned} πSno,Sname(σSsex=’女’(Student))\pi_{\mathrm{Sno},\mathrm{Sname}} \left( \sigma_{\mathrm{Ssex}=\text{'女'}}(\mathrm{Student}) \right)

题目2:查询选修 C01\mathrm{C01} 且成绩大于 8080 分的学生学号

Section titled “题目2:查询选修 C01\mathrm{C01}C01 且成绩大于 808080 分的学生学号”
πSno(σCno=’C01’Grade>80(SC))\pi_{\mathrm{Sno}} \left( \sigma_{\mathrm{Cno}=\text{'C01'} \land \mathrm{Grade}>80}(\mathrm{SC}) \right)

题目3:查询选修 C01\mathrm{C01} 的学生姓名

Section titled “题目3:查询选修 C01\mathrm{C01}C01 的学生姓名”
πSname(StudentσCno=’C01’(SC))\pi_{\mathrm{Sname}} \left( \mathrm{Student} \bowtie \sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC}) \right)

题目4:查询没有选修 C01\mathrm{C01} 的学生学号

Section titled “题目4:查询没有选修 C01\mathrm{C01}C01 的学生学号”
πSno(Student)πSno(σCno=’C01’(SC))\pi_{\mathrm{Sno}}(\mathrm{Student}) - \pi_{\mathrm{Sno}} \left( \sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC}) \right)

这里不能只从 SC\mathrm{SC} 中找课程号“不等于 C01\mathrm{C01}”的记录,因为一个学生可能同时选了 C01\mathrm{C01} 和其他课程。

题目5:查询同时选修 C01\mathrm{C01}C02\mathrm{C02} 的学生学号

Section titled “题目5:查询同时选修 C01\mathrm{C01}C01 和 C02\mathrm{C02}C02 的学生学号”
πSno(σCno=’C01’(SC))πSno(σCno=’C02’(SC))\pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC})\right) \cap \pi_{\mathrm{Sno}}\left(\sigma_{\mathrm{Cno}=\text{'C02'}}(\mathrm{SC})\right)

题目6:查询选修课程学分大于等于 33 的学生姓名和课程名称

Section titled “题目6:查询选修课程学分大于等于 333 的学生姓名和课程名称”
πSname,Cname(StudentSCσCredit3(Course))\pi_{\mathrm{Sname},\mathrm{Cname}} \left( \mathrm{Student} \bowtie \mathrm{SC} \bowtie \sigma_{\mathrm{Credit} \ge 3}(\mathrm{Course}) \right)

题目7:查询选修了所有课程的学生姓名

Section titled “题目7:查询选修了所有课程的学生姓名”

先得到满足条件的学号,再与学生关系连接:

πSname(Student(πSno,Cno(SC)÷πCno(Course)))\pi_{\mathrm{Sname}} \left( \mathrm{Student} \bowtie \left( \pi_{\mathrm{Sno},\mathrm{Cno}}(\mathrm{SC}) \div \pi_{\mathrm{Cno}}(\mathrm{Course}) \right) \right)
关系代数SQL中的对应思路
σF(R)\sigma_F(R)SELECT ... FROM R WHERE F
πA,B(R)\pi_{A,B}(R)SELECT DISTINCT A, B FROM R
RFSR \bowtie_F SR JOIN S ON F
RSR \cup SUNION
RSR - SEXCEPT 或差集逻辑
RSR \cap SINTERSECT
R÷SR \div S通常用双重 NOT EXISTS 或分组计数表达

对照 SQL 有助于理解,但闭卷题目要求写关系代数时,必须写正确符号和运算顺序。


题目只要求姓名,却把整个连接结果写出来。正确表达式最外层通常要有投影:

πSname()\pi_{\mathrm{Sname}}(\cdots)

下面这种写法是错误的:

πSno(σCno’C01’(SC))\pi_{\mathrm{Sno}} \left( \sigma_{\mathrm{Cno} \ne \text{'C01'}}(\mathrm{SC}) \right)

该结果会错误包含“既选了 C01\mathrm{C01} 又选了其他课程”的学生。正确方法使用差集:

πSno(Student)πSno(σCno=’C01’(SC))\pi_{\mathrm{Sno}}(\mathrm{Student}) - \pi_{\mathrm{Sno}} \left( \sigma_{\mathrm{Cno}=\text{'C01'}}(\mathrm{SC}) \right) σ: 筛行,下面标注筛选条件π: 选列,下面标注保留属性\sigma:\ \text{筛行,下面标注筛选条件} \qquad \pi:\ \text{选列,下面标注保留属性}

11.4 看到“所有”却没有想到除法

Section titled “11.4 看到“所有”却没有想到除法”

题干中只要出现“所有课程”“每个项目”“全部指定对象”,就优先判断是否需要使用除运算 ÷\div


关系代数以关系为运算对象,以关系为运算结果。传统集合运算包括并、差、交和笛卡尔积;专门的关系运算包括选择、投影、连接和除。

选择运算 σ\sigma 用于筛选满足条件的元组,即筛行;投影运算 π\pi 用于选取需要的属性,即选列;连接运算 \bowtie 用于根据相关属性把不同关系中的数据组合起来;自然连接会去掉重复的同名连接属性;除运算 ÷\div 常用于表达“满足全部条件”的查询。

写表达式时应先确定需要哪些关系和联系,再写筛选条件,最后投影题目要求输出的属性。查询“没有”通常用差运算,查询“全部”通常考虑除运算。