数据库关系代数习题拆解:从SQL原理到实例验证

发布时间:2026/10/12 1:13:08
数据库关系代数习题拆解:从SQL原理到实例验证
简介数据库关系代数习题是一份用于训练关系代数查询表达式的专项练习文件面向数据库初学者、备考数据库原理课程考试的考生以及需要巩固关系运算能力的读者。内容以学生-课程-学习等经典关系模式切入精选多道典型习题覆盖选择、投影、连接、差运算、除法等核心操作并给出“英语”专业课程信息检索、“数据库原理”90分以上筛选、排除未选C135课程学生等具体示例的表达式与解析。同时涵盖供应商-零件-工程等常用业务模式帮助读者在不同数据场景下灵活应用关系代数。除关系代数检索题外还包含E-R模型设计及向关系模式转换的配套练习可同步检验概念理解与建模能力。每道题均附有参考答案和简要说明便于对照自查。文件采用PDF格式共1个文件大小仅304KB排版紧凑方便在电脑或手机上随时查看。该资料已有2805人学习使用适合课堂随练、课后自测与考前集中复习。1. 关系代数习题的价值它逼你回答SQL背后那个为什么数据库关系代数习题掂在手里不过几十页很多人翻开看到满纸的σ、π、⋈就犯怵觉得不如直接写SQL实在。但等真正上手做题会发现它逼着你回答SQL背后那个最关键的为什么——为什么这个查询要用连接而不是子查询为什么选了全部课程不能一条JOIN写完为什么投影之后有些属性就消失了这些恰恰是日常写SQL时最容易一带而过的地方。以数据库关系代数习题.pdf为代表的这类习题集把这些追问全部拆成了一道道短小题适合三类人正在学数据库原理的学生、准备面试想梳理查询本质的开发者、以及写了多年SQL却说不出底层语义的老手。这一篇按我刷题和带人的经验讲清楚习题怎么拆、坑在哪、怎么验证。2. 六个基本操作与四种出题句式先把符号和题干语言对齐关系代数习题难在第一步把中文题干翻译成符号序列。这一步卡住后面即使会求值也写不对。所以这一章先把操作符号和题干语言两套东西对齐。2.1 六大操作符号、语义与一个记忆锚点关系代数的基本操作有六个选择(σ)、投影(π)、并(∪)、差(−)、笛卡尔积(×)、重命名(ρ)。交(∩)、连接(⋈)、除(÷)都是派生操作由基本操作组合而来习题里几乎必考后面单独展开。操作符号作用SQL对应物易错点选择σ按条件过滤行WHERE条件属性必须在关系里投影π按列裁剪并去重SELECT DISTINCT裁剪后属性不可再引用并∪纵向合并两个关系UNION要求属性结构相同差−去掉另一关系中的元组EXCEPT / NOT IN同样要求关系相容笛卡尔积×两个关系所有行组合CROSS JOIN行数爆炸但语义简单重命名ρ给关系或属性换名AS 别名自连接必备选择σ是行方向过滤σ_成绩90(SC)返回成绩大于90分的所有选课记录。投影π是列方向裁剪π_学号,姓名(学生)只保留学号和姓名两列。这里有个新手最常忽略的点关系代数里的投影默认去重SQL里的SELECT默认不去重所以π_系别(学生)返回的是系别取值集合而不是重复的学生记录。这个差异在习题的计数类问题里尤其明显题目问有哪些系别和有多少条记录是两个完全不同的表达式。并∪和差−是一对集合操作。R−S表示从R中去除S里也出现的元组。交可以用差来定义R∩S R−(R−S)这个推导关系在习题里经常作为隐含考点。笛卡尔积×是连接的基础学生5行×选课8行就是40行多表查询本质上是在笛卡尔积的结果里筛出满足连接条件的元组。重命名ρ在自连接场景必用员工表同时存在工号和经理工号两列时不重命名就没法区分同一张表的两个实例。记一个锚点就够选择管行、投影管列、并差管集合、笛卡尔积开列、重命名救自连接。2.2 四种出题句式把中文题干翻译成代数符号做题的第一步不是写符号是认句式。我练过的一批习题里95%的题干逃不出四种句式。句式一查询满足条件的X的Y典型题干是查询计算机系年龄小于20岁的学生姓名。对应先选择后投影的固定序列π_姓名(σ_系别计算机∧年龄20(学生))。句式二查询与B有关系的A典型题干是查询选修了数据库课程的学生姓名至少涉及两张表先自然连接再选择再投影。句式三查询A但没B典型题干是查询选修了课程1但没选修课程2的学生学号核心是差运算。句式四查询所有/全部课程都……典型题干是查询选修了全部课程的学生姓名最绕除运算或反证法二选一。这四种句式不是按题目难度分的是按语义结构分的。同一道题可以换着说法考你比如没选过任何课的学生其实是句式三的变体哪些课程所有学生都选了其实是句式四的变体。我带着学生练题时要求他们在写表达式之前先给题干贴一个句式标签正确率明显比直接上手写符号高。2.3 求值顺序与优先级别让表达式产生歧义关系代数表达式和算术一样有优先级问题。常规约定是括号最高一元操作选择、投影、重命名次之笛卡尔积和连接再次并、差、交优先级最低。比如π_姓名(σ_系别计算机(学生))最内层是学生然后选择最后投影从内往外读就是一个先选行、再裁列的流程。但很多教材习题答案写得很随意σ紧贴关系时不加括号。考试判卷按标准优先级理解没问题实际做题时我建议全都加满括号宁可多写两对也不要让优先级产生歧义。举一个具体例子σ_条件(学生×选课) 和 (σ_条件(学生))×选课前者先把所有行组合起来再过滤后者先过滤学生再和选课组合行数可能差一大截。题目要求的是存在匹配选课记录的学生后者才是正确语义。还有一个细节一元操作从内到外求值二元操作同优先级时从左往右结合。这些不需要死记遇到复杂表达式就拿一个小数据集手工演算一遍优先级的作用立刻清楚了。练习册里的综合题通常把十多个关系嵌套在一起第一批下来的大多不是不会写σπ而是括号位置放错了。3. 手把手拆解四类高频习题从题目文本到关系代数表达式有了符号基础和句式意识之后接下来就是实战。这一章按句式分类拆四类题每一类给出可复制的推演套路。3.1 单表查询查询满足条件的X的Y先给一个最简单也最典型的例子。关系模式学生(学号,姓名,系别,年龄)。题干查询计算机系年龄小于20岁的学生姓名和学号。第一步确认关系模式里的属性第二步把条件合并进选择操作第三步把目标列放进投影。表达式如下π_学号,姓名(σ_系别计算机 ∧ 年龄20(学生))这个表达式里选择条件用了逻辑与AND关系代数里写成∧。先执行σ把行筛掉再执行π把列裁掉。如果反过来先π投影σ条件里的系别和年龄已经被裁掉选择就无法进行。所以单表查询的顺序原则是先选择后投影。再说一个进阶变化查询年龄最小的学生姓名。没有聚合函数关系代数处理这种最值问题要靠对比。思路是找出所有存在比他更年轻学生的学生再从全体里减掉。π_姓名(学生) − π_姓名(σ_学生.年龄 学生2.年龄(学生 × ρ_学生2(学生)))这里ρ_学生2(学生)把学生表复制一份起名学生2两个实例做笛卡尔积条件是学生表里的年龄大于学生2表里的年龄筛出来的就是那些年龄不是最小的学生减去它们剩下年龄最小的。重命名、笛卡尔积、差运算一次全考到了是单表习题里综合性最高的一类。3.2 多表关联连接类习题的三种典型形态连接类习题分三种典型形态难度递进。第一种是二表连接第二种是三表连接第三种是自连接。三种形态对应的模式不同但套路一致先连接所有涉及的关系再选择最后投影。二表连接的例子查询有选课记录的学生姓名。学生(学号,姓名,系别)、选课(学号,课程号,成绩)两张表都有学号自然连接自动匹配π_姓名(学生 ⋈ 选课)自然连接会把同名列的值相等的元组组合起来选课里没有记录的学生自然就被滤掉了。这里要小心如果答案写成等值连接同名列会保留两份结果关系里出现两个学号列后续引用属性时容易出错。三表连接查询选修了数据库课程的学生姓名。学生、选课、课程三张表学生和选课通过学号连接选课和课程通过课程号连接。整个表达式是π_姓名(σ_课程名数据库(学生 ⋈ 选课 ⋈ 课程))先自然连接三张表生成一张包含所有属性的宽表再按课程名过滤最后投影姓名。自然连接满足结合律所以顺序随便写但属性冲突时要注意同名列合并行为。如果三张表里有两个同名列且语义不同题目通常会要求你用等值连接并显式写出连接条件。自连接查询工资比直属经理高的员工姓名。员工(工号,姓名,工资,经理工号)。经理也是员工同一张表要出现两次必须重命名π_员工.姓名(σ_员工.工资 经理.工资(员工 ⋈_员工.经理工号经理.工号 ρ_经理(员工)))ρ_经理(员工)把员工表复制一份并命名为经理然后用等值连接把员工的经理工号与经理的工号对上再比较工资。这是自连接的标准写法也是习题册里区分会连接和真懂连接的分水岭。3.3 但没/除外题型用差运算表达否定语义但没、除外、没选过这类否定语义在关系代数里对应差运算。最经典的例子查询选修了课程1但没选修课程2的学生学号。π_学号(σ_课程号1(选课)) − π_学号(σ_课程号2(选课))先分别找出选了课程1和课程2的学生学号集合然后做集合减法得到的就是选了课程1而不是课程2的学生。这里的关键是两侧的投影属性必须一致都是学号这样结果才是一个可比较的学号集合。变体题目查询没有选修任何课程的学生姓名。先找出全体学号和已选课学号的差集再和学生表连接取姓名π_姓名(学生 ⋈ (π_学号(学生) − π_学号(选课)))实际做题时经常有人把差运算写成不在IN列表里的直觉用NOT EXISTS去翻译这在SQL里没问题但关系代数里只能用−。注意差运算两侧的关系结构必须完全一致否则连合法性都不满足这一点在第4章的避坑部分重点展开。3.4 所有/全部题型除运算与反证法两条路线四类句式里最绕的是所有/全部。典型题干查询选修了全部课程的学生学号。关系模式选课(学号,课程号)、课程(课程号,课程名)。直接解法是用除运算π_学号,课程号(选课) ÷ π_课程号(课程)除运算的语义是找出那些在除数关系课程的每一个课程号上都有对应选课记录的学生学号。被除关系的属性集合必须包含除数关系的属性集合结果是被除关系剩余属性上的投影。但除运算符号本身不直观很多教材也不展开推导。更稳妥、也更便于理解的是反证法。先找出至少有一门课没选的学生再从全体学生里减掉。具体分三步第一步全体学生与全部课程做笛卡尔积得到理论上所有可能的选课组合第二步减去实际选课记录得到应该有却没有的组合第三步对这个组合做学号投影就是有缺课的学生。π_学号(学生) − π_学号(π_学号(学生) × π_课程号(课程) − π_学号,课程号(选课))这个表达式的境界在于π_学号(学生)×π_课程号(课程) 生成了每个学生和每门课的理论选课组合把实际选课表减掉之后剩下的就是缺的选课记录再投影学号得到缺课的学生最后用全体学生做差。整个过程完全用基本操作表达不需要除运算符号。考试时我建议优先写反证法不容易出错判卷老师也容易看懂。4. 关系代数习题避坑指南五个让人反复翻车的细节这一部分是我刷题带人积累下来的血泪经验。关系代数符号不多但坑全藏在细节里每一条都有具体的现象、原因和解决路径。4.1 投影裁掉连接属性后续操作全盘皆输现象写表达式时图省事第一步就把目标之外的所有属性投影掉。等做到下一步需要按学号连接时发现学号根本不在当前关系里。原因投影是列方向裁剪裁掉的属性在当前关系里彻底消失没有任何字段可以把它找回来。很多人写SQL时习惯了SELECT之后再JOIN因为SQL的SELECT并不真的把列丢掉但关系代数里投影是不可逆的。解决把投影放到表达式最后一环。中间步骤用完整关系或只做选择最后再投影结果列。如果必须在中间投影就把后续还要用的属性一并保留。做题时先写整体结构再确定投影时机可以有效避免这个问题。4.2 自然连接与等值连接混用同名列去留不同现象同一个连接条件有时答案里同名列只剩一份有时却保留两份不知道哪个对。原因自然连接自动把同名列等值匹配并在结果关系里合并为一份等值连接需要显式写出连接条件结果关系里两个同名列都会保留。很多习题关系模式设计成学号在学生表叫学号、在选课表也叫学号用自然连接刚好对上。但一旦涉及经理工号和工号这类不同名属性就只能用显式等值连接。解决先看两边的属性名是否一致。一致且题面没有特殊说明用自然连接不一致时别硬凑自然连接显式写出 学生.学号 选课.学号 这类条件。自连接场景必须显式等值连接因为同一张表的同名属性如果不重命名自然连接会把同名列合并导致语义完全错误。4.3 集合操作不检查关系相容结果直接报错现象把选课关系和课程关系做并集运算结果完全没有意义或直接报错。原因并、差、交运算要求两个关系的属性数量相同、对应属性的域相同。选课(学号,课程号,成绩)和课程(课程号,课程名)属性数不同差运算两侧如果一边是学号一边是姓名学号和姓名的域也不兼容。解决执行集合操作之前先对两个关系做投影对齐结构。比如要得到选了课程1或课程2的学生学号并集先分别投影出学号再做∪要做差也一样先把两侧都投影成纯学号集合。如果在练习册答案里看到类似 π_学号(σ_课程号1(选课)) − π_学号(σ_课程号2(选课))两侧结构完全一致这就是相容性的典型写法。4.4 除运算弄反方向返回一堆无意义元组现象写选修了全部课程的学生把除运算的被除数和除数写反结果返回一堆乱七八糟的元组。原因除运算的语义是被除关系的属性集合包含除关系的属性集合结果是被除关系独有的属性上那些能和除数每一个元组都匹配的值。写反了就是把课程当被除数但课程表里根本没有学号属性结果自然是错的。解决凡事涉及全部课程都……的先确认被除关系的属性必须覆盖除关系的全部属性。选课(学号,课程号)÷课程(课程号)选课包含课程号结果按学号投影正确。如果被除关系没有学号那就构不成这个题。拿不准的时候直接换反证法用第3.4节的三步表达式逻辑更直白不容易被除运算的符号绕晕。4.5 重命名被忽略自连接问题寸步难行现象同一张表做两次连接时不重命名表达式写出来属性名全是环境变量式的模糊指代结果完全对不上。原因关系代数里每次引用关系都需要一个明确的实例名。同表出现两次如果不重命名两个实例都叫员工条件里写员工.工资员工.工资根本不知道哪个是员工、哪个是经理。解决自连接第一件事就是重命名ρ_经理(员工)把员工表复制成经理实例再通过连接条件区分角色。写表达式时给每个实例配一个有意义的名字比用ρ_员工2这种编号强得多。这个习惯也能让后续手工求值省不少力气。5. 把表达式翻译成SQL用真库验证习题答案的正确性做完了题如何确定答案是对的最可靠的办法是把关系代数表达式翻译成SQL在小数据库里真跑一遍。5.1 关系代数与SQL的映射表一个操作一个习惯关系代数SQL实现注意事项σ_条件(R)SELECT * FROM r WHERE 条件条件直接迁移π_列(R)SELECT DISTINCT 列 FROM r必须加DISTINCTSQL默认不去重R ∪ SSELECT ... UNION SELECT ...SQL的UNION自带去重R − SSELECT ... EXCEPT SELECT ...SQLite/PostgreSQL用EXCEPTMySQL用NOT INR × SSELECT ... FROM r CROSS JOIN s实际少用通常被JOIN替代自然连接 R⋈SSELECT ... FROM r NATURAL JOIN s注意隐式连接条件等值连接SELECT ... FROM r JOIN s ON r.as.a显式条件最常写有一个和教材存在差异的坑关系代数投影默认去重SQL的SELECT不去重。如果题目是查询有哪些系别关系代数答案π_系别(学生)不会有重复值翻译SQL时必须写SELECT DISTINCT 系别不写就翻车。集合运算同理UNION默认去重对应关系代数的∪UNION ALL对应不去重版本。MySQL没有EXCEPT关系代数里的R−S在MySQL里要用NOT EXISTS子查询或LEFT JOIN WHERE IS NULL来实现。如果你用SQLite练题SQLite支持EXCEPT和INTERSECT和关系代数语义完全一致是最适合验证习题答案的环境。5.2 复合表达式翻译三步法从内层括号开始剥拿到一个复合表达式不要从头写到尾按三层来翻译。第一层把最内层括号里的子表达式翻译成一条SELECT第二层把外层操作包到已有SELECT的外面第三层最后处理投影和选择顺序。举具体的例子把第3.4节的反证法表达式翻译成SQL。对应关系模式-- 学生表学号、姓名、系别 CREATE TABLE student ( sno TEXT PRIMARY KEY, sname TEXT NOT NULL, dept TEXT NOT NULL ); -- 选课表学号、课程号、成绩 CREATE TABLE sc ( sno TEXT NOT NULL, cno TEXT NOT NULL, grade INT, PRIMARY KEY (sno, cno) ); -- 课程表课程号、课程名 CREATE TABLE course ( cno TEXT PRIMARY KEY, cname TEXT NOT NULL );这三条建表语句里sno和cno是逻辑外键分别指向student和course。把表建好之后逐层翻译。最内层是π_学号,课程号(选课)翻译成SELECT DISTINCT sno, cno FROM sc;然后做全体学生与全部课程的笛卡尔积减去实际选课记录翻译时这一步用EXCEPT更直观SELECT s.sno FROM student s CROSS JOIN course c EXCEPT SELECT sc.sno FROM sc;这句SQL的逻辑是先让每个学生和每门课程组合得到理论上的全部选课组合再用EXCEPT去掉实际存在于选课表的组合剩下的就是应该有却没选的记录投影出学号就是缺课学生。最后一步是从全体学生里减去缺课学生SELECT sno FROM student EXCEPT SELECT s.sno FROM student s CROSS JOIN course c EXCEPT SELECT sc.sno FROM sc;这里把两步EXCEPT串联起来第一段取全体学生第二段去掉有缺课记录的学生最终得到选修了全部课程的学生学号。执行这条SQL后对照习题册标准答案结果一致说明表达式正确。5.3 用小数据集跑SQL结果集对不上就看这三处翻译成SQL跑通之后如果结果和教材答案对不上优先排查三处第一处是DISTINCT有没有加关系代数投影默认去重SQL忘记DISTINCT会把重复元组留在结果里行数偏大。第二处是连接条件是否完全等价自然连接在SQL里用NATURAL JOIN但NATURAL JOIN会把所有同名属性都作为连接条件如果两表除了外键还有其它同名列结果可能比预期少很多行这时应该改用显式ON条件。第三处是空值问题SQL的三值逻辑会把NULL过滤掉关系代数习题通常默认没有NULL但真实数据里NULL会让没选课的语义翻车遇到对不上的结果先把数据里的NULL清掉再对比。6. 一个自查技巧用SQLite内存表十分钟验证整套习题答案最后一章给一个我实际用了很久的技巧用SQLite内存表批量验证习题答案。不需要安装任何额外服务命令行里直接跑。第一步把第5章的三张表建起来插入一组覆盖边界情况的小数据比如一个选了两门课的学生、一个只选了一门课的学生、一个完全没选课的学生、一门无人选的课程。第二步把习题册里每道题的表达式翻译成SQL存成一个solutions.sql文件逐条执行。第三步把每条查询的结果和习题答案比对重点看行数和关键值。具体落地时可以用bash把整个流程串起来sqlite3 :memory: EOF .read schema.sql .read solutions.sql SELECT zhaoyang, COUNT(*) FROM ( SELECT sno FROM student EXCEPT SELECT s.sno FROM student s CROSS JOIN course c EXCEPT SELECT sc.sno FROM sc ); EOF这段bash脚本中:memory:让SQLite在内存里建库schema.sql负责建表插数据solutions.sql存放翻译好的查询语句。末尾那条COUNT(*)把选修了全部课程的学生查出来数个数和习题答案对一下行数就知道对不对。每个表达式都包一层COUNT比肉眼比对结果集高效得多。我拿这个方法验证过两本习题册前后抓出过三个问题一个是我自己投影时机写错一个是自然连接和等值连接混用还有一个是习题答案在除运算方向上的小瑕疵。从那以后我养成了一个习惯——凡是关系代数综合题一定先翻译SQL再交答案。手工推导的表达式写得再流畅也抵不上在真实数据库里跑一条SELECT来确认。希望能帮你在关系代数这条路上少踩几个坑。本文还有配套的精品资源点击获取