十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

关系代数详解:从选择、投影到连接运算的SQL对照与实战

关系代数详解:从选择、投影到连接运算的SQL对照与实战 关系代数是数据库管理系统中最基础、也最容易让初学者绕晕的一块内容。它不像 SQL 那样有明确的SELECT、WHERE、JOIN关键字而是靠一堆希腊字母符号来描述“怎么从表里拿数据”。很多人在学《数据库系统概论》时看到 σ、π、⋈、÷ 就卡住了其实关系代数的核心思想非常朴素用集合运算的方式描述查询过程。这篇文章就以 Neso Academy 的《数据库管理系统》课程里关于关系代数的内容为主线把第一部分的核心知识点拆开讲清楚。我们会先明确关系代数在整个数据库课程中的位置然后逐一把选择、投影、并、差、笛卡尔积、连接、除这些运算的语法、语义、示例和 SQL 对照写出来。每一类运算都会配上具体的表和结果保证看完就能照着推导。内容适合三类读者正在准备数据库期末考试或考研的学生、刚接触 SQL 想搞清楚底层查询逻辑的开发者、以及面试前需要快速复习关系代数知识点的人。1. 关系代数知识点速览知识点运算符号SQL 对照典型场景选择σWHERE筛选满足条件的行投影πSELECT DISTINCT选取指定列并去重并∪UNION合并两个相容关系的元组差−EXCEPT找出在一个关系中但不在另一个关系的元组笛卡尔积×CROSS JOIN两个关系所有行两两组合更名ρAS给关系或属性起别名交∩INTERSECT找出两个关系共有的元组θ 连接⋈θJOIN ... ON按条件连接两个关系等值连接⋈JOIN ... ON等值条件按相等条件连接自然连接⋈NATURAL JOIN按同名属性等值连接并去重外连接⟕ ⟖ ⟗LEFT/RIGHT/FULL OUTER JOIN保留未匹配元组除÷通常用NOT EXISTS实现查询“包含全部”类问题从学习顺序上看关系代数可以分成三层基础集合运算选择、投影、并、差、笛卡尔积、更名。连接运算θ 连接、等值连接、自然连接、外连接。扩展集合运算交、除。这三层是递进关系前面没掌握后面很容易懵。2. 适用场景与学习边界关系代数在数据库课程中的定位是“描述性查询语言”的理论基础。它不是一门可以直接运行的语言而是一种形式化工具。学习它真正的价值在于理解 SQL 的底层逻辑SQL 的SELECT ... FROM ... WHERE ...本质上是投影、笛卡尔积、选择这三个运算的组合。搞清楚关系代数你写 SQL 时就不容易把WHERE和HAVING混用也能理解为什么JOIN之前要先做笛卡尔积。看懂数据库执行计划MySQL、PostgreSQL、Oracle 的查询优化器会把 SQL 翻译成关系代数表达式再生成执行计划。EXPLAIN输出里的Nested Loop、Hash Join本质上就是在执行某种连接运算。应对考试和面试关系代数是数据库原理笔试的高频考点尤其是自然连接、除运算、查询表达式书写这类题目。不过也要说清楚边界实际开发中你不会去写关系代数表达式它只是用来描述查询意图的。真正落地靠的还是 SQL或者是 JPA、MyBatis 这类持久层框架生成的 SQL。所以学习时不要钻牛角尖重点是把“每个运算改变什么”搞清楚。3. 关系模型基础概念在开始写关系代数表达式之前先把关系模型里的基础概念对齐。这些概念在后面每一个运算中都会用到。术语英文含义对应 SQL 中的概念关系Relation一张二维表Table元组Tuple表中的一行Row属性Attribute表中的一列Column域Domain属性取值的合法集合数据类型 约束关系模式Relation Schema关系的结构描述CREATE TABLE定义关系实例Relation Instance某一时刻关系中的数据表里的数据快照基数Cardinality关系中元组的数量行数度数Degree关系中属性的数量列数举个例子假设有个教师关系instructor(ID, name, dept_name, salary)这个instructor(ID, name, dept_name, salary)就是关系模式表示这个关系有四个属性。而具体存入的数据IDnamedept_namesalary10101SrinivasanComp. Sci.6500012121WuFinance9000015151MozartMusic40000这就是关系实例。元组数量是 3基数就是 3属性数量是 4度数就是 4。关系代数里的所有运算都基于一个前提每个关系的元组集合是无序的属性之间没有默认顺序但同一个关系模式中的属性按定义顺序引用。运算结果仍然是一个关系这就是“闭合性”Closure Property。4. 基本运算详解4.1 选择运算 σSelection选择运算是从关系中选出满足条件的元组。它作用于行不改变列。语法σ条件(关系)条件里可以使用比较运算符, ≠, , ≥, , ≤也可以用逻辑连接词∧ (and)、∨ (or)、¬ (not)。举例查询工资大于 80000 的教师σsalary 80000(instructor)结果是IDnamedept_namesalary98345KimElec. Eng.80000实际判断标准是从上到下逐行检查条件满足条件的行保留不满足的丢弃。这里要求输出列不变所以选择运算的结果和原关系有相同的度数。查询计算机科学系且工资大于 70000 的教师σdept_name Comp. Sci. ∧ salary 70000(instructor)SQL 对照SELECT * FROM instructor WHERE dept_name Comp. Sci. AND salary 70000;选择运算的注意点条件里引用的属性必须存在于该关系中。字符串比较需要保持大小写一致具体行为取决于数据库的排序规则。多个条件用 ∧ 连接时每个条件都要写清楚属性名。4.2 投影运算 πProjection投影运算是从关系中选出指定的列并去除重复元组。它作用于列不改变行数但可能因为去重而减少行数。语法π属性列表(关系)举例查询所有教师的姓名和工资πname, salary(instructor)假设原表中有两名教师同名且工资相同投影结果里只会保留一个元组。这是关系代数和 SQL 的一个重要差异点SQL 的SELECT name, salary FROM instructor默认不去重而关系代数的投影是集合运算天然去重。所以 SQL 的精确对照应该是SELECT DISTINCT name, salary FROM instructor;投影运算的注意点投影列必须来自原关系不能凭空造列。投影结果中属性的顺序可以调整这正是关系代数灵活性的体现。如果投影列表中有多个属性输出结果中这些属性按表达式书写顺序排列。4.3 并运算 ∪Union并运算把两个关系的元组合并成一个关系。关键前提是两个关系必须相容属性数量相同。对应属性的域相同或兼容。语法关系1 ∪ 关系2举例查询 2017 年秋季开课的所有课程号和 2017 年春季开课的所有课程号假设section表如下course_idsemesteryearCS-101Fall2017CS-101Spring2018CS-347Fall2017PHY-101Fall2017用并运算πcourse_id(σsemester Fall ∧ year 2017(section)) ∪ πcourse_id(σsemester Spring ∧ year 2017(section))结果是四个course_id中满足条件的并集且重复的CS-101只保留一次。SQL 对照SELECT course_id FROM section WHERE semester Fall AND year 2017 UNION SELECT course_id FROM section WHERE semester Spring AND year 2017;4.4 差运算 −Difference差运算从第一个关系中删除第二个关系里也存在的元组。两个关系同样需要相容。语法关系1 − 关系2举例查询 2017 年秋季开课但不在 2017 年春季开课的所有课程号πcourse_id(σsemester Fall ∧ year 2017(section)) − πcourse_id(σsemester Spring ∧ year 2017(section))如果秋季开课是CS-101, CS-347春季开课是CS-101, PHY-101结果就是CS-347因为CS-101在春季也开课了。SQL 对照SELECT course_id FROM section WHERE semester Fall AND year 2017 EXCEPT SELECT course_id FROM section WHERE semester Spring AND year 2017;注意MySQL 8.0 之前没有EXCEPT关键字要用NOT IN或LEFT JOIN ... IS NULL实现。PostgreSQL、SQL Server 原生支持EXCEPT。4.5 笛卡尔积 ×Cartesian Product笛卡尔积把两个关系的所有元组两两组合生成一个新关系。语法关系1 × 关系2结果关系的属性数量等于两个关系度数之和元组数量等于两个关系基数之积。举例instructor × teaches如果instructor有 3 个元组teaches有 5 个元组结果就有 15 个元组。这样生成的很多组合在业务上毫无意义所以笛卡尔积通常要配合选择运算来用σinstructor.ID teaches.ID(instructor × teaches)这就是“先做笛卡尔积再按条件筛选”的经典套路。SQL 中的CROSS JOIN就是笛卡尔积SELECT * FROM instructor CROSS JOIN teaches;或者老式写法SELECT * FROM instructor, teaches;在实际数据库里直接做笛卡尔积通常很危险数据量稍大就会产生爆炸性结果。所以写 SQL 时要始终记得加连接条件。4.6 更名运算 ρRename更名运算给关系或属性起别名方便在表达式里区分不同关系中的同名属性。语法ρ新关系名(属性1, 属性2, ...)(旧关系)也可以只给关系起名ρ新关系名(旧关系)举例查询所有教师的完整信息并给结果起个别名叫TρT(instructor)在连接运算里更名特别有用。比如要查询“工资比某个教师高的其他教师”就需要同一个关系同时以两个不同名字出现ρT1(instructor) × ρT2(instructor)这样T1和T2就可以分别当不同的表来引用最后再通过T1.salary T2.salary做选择。SQL 对照就是AS关键字SELECT * FROM instructor AS T1, instructor AS T2 WHERE T1.salary T2.salary;5. 连接运算详解连接运算是关系代数里最核心、也最容易出错的部分。它本质上可以看作“笛卡尔积 选择条件”的简写。5.1 θ 连接Theta Joinθ 连接在笛卡尔积的基础上增加了连接条件的限制。θ 代表任意比较运算符, ≠, , , ≥, ≤。语法关系1 ⋈θ 关系2等价于σθ(关系1 × 关系2)举例查询所有教师和课程安排的组合只保留教师 ID 匹配的行instructor ⋈instructor.ID teaches.ID teaches这个表达式的完整推导是σinstructor.ID teaches.ID(instructor × teaches)结果是两个关系按ID相等条件连接后的新关系。5.2 等值连接Equi-Join等值连接是 θ 连接的特殊情况连接条件只包含相等运算符。语法关系1 ⋈关系1.A 关系2.A 关系2等值连接的结果里连接属性会出现两次。比如instructor.ID和teaches.ID都会出现在结果里只是来自不同的关系。有些教材里把这种情况称为“重复属性”。5.3 自然连接 ⋈Natural Join自然连接是等值连接的进一步简化它自动匹配两个关系中所有同名属性要求这些属性的值相等并且结果中不重复列出同名属性。语法关系1 ⋈ 关系2自然连接和等值连接的区别等值连接需要显式指定连接条件。自然连接自动查找同名属性如果两个关系没有同名属性自然连接就退化成了笛卡尔积。自然连接的结果中同名属性只保留一列。举例instructor ⋈ teaches假设instructor和teaches都有ID属性自然连接会找出ID相等并且course_id等条件匹配的行同时删除重复的ID列。结果是一个包含ID, name, dept_name, salary, course_id, sec_id, semester, year的关系。SQL 对照SELECT * FROM instructor NATURAL JOIN teaches;自然连接有一个比较坑的地方如果两个表有多个同名列它会自动把所有这些列都纳入连接条件不一定符合业务预期。比如teaches表如果有year和semestercourse表也有year和semester那么course ⋈ teaches会要求年份、学期都相等结果可能出乎意料。所以生产环境的 SQL 里我一般建议写成显式JOIN ... USING或ON避免默认同名匹配带来歧义。5.4 外连接Outer Join外连接在自然连接或 θ 连接的基础上保留未匹配的元组缺失的属性用null填充。三种外连接类型符号行为左外连接⟕保留左关系所有元组右外连接⟖保留右关系所有元组全外连接⟗保留两边所有元组举例查询所有学生的选课信息即使没选课的学生也要保留student ⟕ takes如果一个学生没有选课takes侧的所有属性会被填为null。SQL 对照SELECT * FROM student LEFT OUTER JOIN takes ON student.ID takes.ID;外连接是实际开发中使用频率极高的连接方式。像“查所有用户及其订单没下过单的用户也要列出来”这种需求就必须用外连接。6. 交运算与除运算6.1 交运算 ∩Intersection交运算找出两个关系共有的元组。两个关系需要相容。语法关系1 ∩ 关系2举例查询 2017 年秋季和 2017 年春季同时开课的课程号πcourse_id(σsemester Fall ∧ year 2017(section)) ∩ πcourse_id(σsemester Spring ∧ year 2017(section))交运算可以表示为差运算的组合R ∩ S R − (R − S)SQL 对照SELECT course_id FROM section WHERE semester Fall AND year 2017 INTERSECT SELECT course_id FROM section WHERE semester Spring AND year 2017;MySQL 8.0 同样不支持INTERSECT需要用IN子查询实现。6.2 除运算 ÷Division除运算是关系代数中最难理解、也最容易在考试里失分的运算。它的含义是找出在 R 中出现的、并且和 S 中所有元组都匹配过的元组。语法R ÷ S一种直观理解方式假设 R 有两个属性组 A 和 BS 只有属性 B。R ÷ S 的结果是属性 A 的一个集合包含所有在 R 中与 S 的每一个 B 值都组合过的 A 值。举例查询选修了全部课程的学生学号。给定选课关系takes(student_id, course_id)和全部课程关系course(course_id)takes(student_id, course_id)course(course_id)如果course表里有CS-101, CS-347, PHY-101三门课那么只有student_id同时选了这三门课的学生才会出现在takes ÷ course的结果里。除运算的表达式可以转换为基本运算R ÷ S πA(R) − πA( (πA(R) × S) − πA,B(R) )这个公式看一眼知道思路就行考试时一般直接按“包含所有 S 中的 B 值”来推理。SQL 对照通常用双NOT EXISTS实现SELECT DISTINCT student_id FROM takes AS T1 WHERE NOT EXISTS ( SELECT course_id FROM course WHERE NOT EXISTS ( SELECT 1 FROM takes AS T2 WHERE T2.student_id T1.student_id AND T2.course_id course.course_id ) );除运算在实际业务中不如连接常用但它是数据库理论考试的重点因为它能很好地区分学生是“背会了”还是“真正理解了关系代数”。7. 关系代数表达式组合与查询示例关系代数的运算可以嵌套组合就像 SQL 里的子查询一样。下面用一个完整示例把前几节的运算串起来。假设有如下关系模式student(student_id, name, dept_name) course(course_id, title, credits) takes(student_id, course_id, semester, year) instructor(instructor_id, name, dept_name, salary) teaches(instructor_id, course_id, semester, year)查询1找出计算机科学系所有学生的姓名。πname(σdept_name Comp. Sci.(student))SQLSELECT name FROM student WHERE dept_name Comp. Sci.;查询2找出工资大于 80000 的教师姓名和他们讲授的课程号。πname, course_id(σsalary 80000(instructor ⋈ teaches))这条表达式要先做instructor ⋈ teaches的自然连接再选择salary 80000的行最后投影name和course_id。SQLSELECT instructor.name, teaches.course_id FROM instructor JOIN teaches ON instructor.instructor_id teaches.instructor_id WHERE instructor.salary 80000;查询3找出至少选修了两门不同课程的学生姓名。这里需要用到自连接。先把takes起两个别名ρT1(takes) ρT2(takes)再按不同课程做连接πT1.student_id(σT1.course_id ≠ T2.course_id(T1 × T2)) ⋈ student最后投影出姓名πname( (πT1.student_id(σT1.course_id ≠ T2.course_id(T1 × T2))) ⋈ student )SQLSELECT s.name FROM student s JOIN takes t1 ON s.student_id t1.student_id JOIN takes t2 ON t1.student_id t2.student_id WHERE t1.course_id t2.course_id;查询4找出选修了 CS-101 且成绩为 A 的学生姓名、学期和年份。πname, semester, year(σcourse_id CS-101 ∧ grade A(takes ⋈ student))这里的核心思路是先把student和takes自然连接再用选择条件过滤最后投影需要的列。SQLSELECT s.name, t.semester, t.year FROM takes t JOIN student s ON t.student_id s.student_id WHERE t.course_id CS-101 AND t.grade A;从这几个例子可以看出关系代数的写法很有规律先用连接把相关表连起来再用选择过滤行最后用投影挑列。这也对应了 SQL 的执行顺序FROM→ON/JOIN→WHERE→SELECT。8. 关系代数与数据库查询执行关系代数不只是考试内容它与数据库管理系统的底层实现直接相关。当你执行一条 SQL 时数据库优化器会做这样几件事把 SQL 解析成抽象语法树。转换成逻辑查询计划即关系代数表达式树。通过等价转换规则进行优化例如把选择下推Predicate Pushdown、调整连接顺序。生成物理执行计划决定使用哪种连接算法Nested Loop Join、Hash Join、Merge Join和访问路径全表扫描、索引扫描。执行并返回结果。以SELECT name, salary FROM instructor WHERE salary 80000为例它的关系代数表达式是πname, salary(σsalary 80000(instructor))对应的表达式树是π(name, salary) | σ(salary 80000) | instructor优化器会考虑是先做选择、减少数据量再做投影还是先投影、减少列宽再做选择。多数情况下先做选择更好因为行数减少后后续运算的代价会更低。这就是“选择下推”的优化策略。在实际数据库里观察执行计划可以用EXPLAINEXPLAIN SELECT name, salary FROM instructor WHERE salary 80000;输出中会看到Filter、Seq Scan或Index Scan等节点对应的就是关系代数里的选择运算和扫描方式。理解了关系代数读懂执行计划会轻松很多。另外关系代数的“闭合性”决定了它可以无限嵌套组合这也正是 SQL 里子查询、CTE、视图能够实现的基础逻辑。视图本质上就是一个有名字的关系代数表达式结果。9. 常见问题与易错点排查问题现象可能原因排查方式解决思路选择条件里用了不存在的属性属性名写错或表里没有该列对照关系模式检查属性名先列出关系的全部属性再写条件投影结果比预期少很多行忘了关系代数的投影会去重对比SELECT DISTINCT的行为明确投影是集合运算去重是正常现象并运算报错或结果异常两个关系不相容属性数量或域不匹配检查两个关系的属性列表先调整投影或更名使两个关系属性一致自然连接结果丢列同名属性被自动合并查看两个关系的公共属性如果不需要自动合并改用显式等值连接自然连接结果特别多多个同名属性被同时匹配或者没有同名属性检查两个关系的 schema考虑用USING指定连接列笛卡尔积结果爆炸忘记写连接条件检查表达式里是否有×连接运算尽量用⋈或σ限制除运算结果总为空S 中包含 R 中不存在的值或匹配关系理解错误手写一遍笛卡尔积验证先算πA(R) × S再对比R做差外连接结果里 null 太多没有理解外连接的保留语义看左右两侧关系哪边被全部保留确认需要保留左表还是右表关系代数表达式与 SQL 结果不一致SQL 默认不去重而关系代数去重用SELECT DISTINCT重新执行对比时在 SQL 中显式加DISTINCT多个连接嵌套时丢数据没注意连接顺序和中间结果的变化分步执行每个运算检查每步输出拆成多个临时关系逐个验证这里重点提醒两点第一自然连接不是万能的。如果两个表有多组同名字段自然连接会把它们全部作为连接条件很容易得出空结果或错误结果。开发中写NATURAL JOIN要非常谨慎建议改用JOIN ... USING或JOIN ... ON。第二关系代数的“投影去重”和 SQL 的“默认不去重”是一个经典考点。笔试时经常出“这个关系代数表达式是否等价于这条 SQL”的判断题本质就是在考这个差异。10. 学习路径与最佳实践关系代数不值得死记硬背关键是理解每个运算改变了什么、最终产生什么结果。建议按下面几步来学先用手算小表格。拿 3 到 5 行的小关系手动推导每一个运算的结果和 SQL 跑出来的结果比对。把每个运算和 SQL 关键字对应起来。每次学一个运算就在 MySQL 或 PostgreSQL 里写一条等价 SQL看输出是否一致。重点攻克自然连接和除运算。这两个是笔试高频题也是理解关系代数难易程度的分水岭。练习组合表达式。给出一个业务需求先写关系代数表达式再写 SQL最后用EXPLAIN观察执行计划。反复练习到“看到查询需求脑中自动浮现连接 → 选择 → 投影”的顺序为止。做错题记录。关系代数的坑比较固定比如投影去重、自然连接丢列、除运算的语义理解。把做错的题分类整理考前看错题比看笔记高效得多。这套流程里最值得先验证的是“手算 SQL 对照”。因为关系代数本身没有标准运行环境唯一的客观验证标准就是 SQL 的结果。你可以在本机装一个 MySQL 8.0 或 PostgreSQL准备好student、course、takes三张测试表然后从最简单的σ开始逐条把表达式转成 SQL 执行。每验证通过一个运算就可以在笔记里标记完成。建议把这篇文章收藏备用作为关系代数第一部分的速查手册后续再看第二部分时就可以直接拿这里的运算规则往上叠加。
返回列表