ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

关系代数查询基础:从选择、投影到连接与除运算全解析

2026/9/7 5:22:44 拓冰建站 浏览量
关系代数查询基础:从选择、投影到连接与除运算全解析 很多人在学数据库管理系统DBMS时习惯直接从 SQL 语法入手建表、写查询、调索引却很少回头理解数据库底层“用什么规则推导出结果”。关系代数就是这一层最基础的语言。它是数据库理论课程里避不开的内容也是理解 SQL、查询优化器、索引选择和事务隔离的基础。本文围绕“关系代数查询”这个主题用一套完整的学生-课程-选课数据作为贯穿示例逐个拆解选择σ、投影π、并∪、差−、笛卡尔积×、交∩、连接⋈、除÷这八个基础运算。每个运算会说明定义、给出直观示例、展示计算过程并提供对应的 SQL 写法。无论你是在准备数据库期末考试还是想从“会写 SQL”进阶到“理解数据库是怎么工作的”这篇内容都适用。这是关系代数查询系列的第一部分重点放在“基础运算”和“查询表达思想”上。第二部分会进一步讨论等值连接、自然连接与外连接的区别、查询优化中的等价变换等进阶话题。建议先跟着本文把基础运算手算一遍再动手建表验证。1. 为什么数据库管理系统要先学关系代数1.1 关系代数在数据库课程中的位置数据库管理系统DBMS解决的核心问题是如何高效地存储和查询数据。大多数现代数据库产品采用关系模型也就是把数据组织成一张张二维表。那么问题来了当我们写出一条 SQL 时数据库内部到底经历了什么大致流程是SQL 语句被解析成语法树。语法树被转换成关系代数表达式。查询优化器对表达式进行等价变换选择执行代价更小的方案。数据库引擎按照优化后的计划扫描数据、连接表、过滤记录。关系代数正好处在第 2 和第 3 步。它像是一种“中间语言”把用户层面的查询意图翻译成对关系表的运算序列。这也是为什么数据库原理教材都会花大量篇幅介绍关系代数。1.2 关系代数和 SQL 是什么关系关系代数是过程化语言它告诉系统“先做哪一步、再做哪一步”SQL 在多数场景下是声明式语言它告诉系统“要什么结果”具体执行顺序由优化器决定。举个例子查询“计算机系学生的姓名”关系代数的写法是π_Sname( σ_DeptCS(Student) )对应的 SQL 是SELECT Sname FROM Student WHERE Dept CS;两者的结果完全相同。对比这两段写法你会发现SQL 的 WHERE 对应选择 σSELECT 的列对应投影 πFROM 对应关系输入。理解关系代数能让你更清楚地知道 SQL 的每一个子句在底层做了什么。2. 关系模型基础概念2.1 关系、元组、属性关系代数操作的对象是“关系”Relation。在数据库里关系就是一张表。为了精确描述运算规则必须先统一几个概念关系一张二维表。元组表中的一行数据。属性表中的一列数据。度关系中属性的个数。基数关系中元组的个数。域某个属性允许取值的集合。比如一张学生表有学号、姓名、性别、年龄、所在系 5 个属性那么它的度是 5。如果表中有 6 行数据基数就是 6。2.2 关系模式与关系实例关系模式描述表的结构通常写作Student(Sno, Sname, Sex, Age, Dept)关系实例则是指某个时刻表中实际存放的数据。关系代数运算针对的是实例也就是真实的数据行。理解这点很重要因为同一个关系模式在不同时刻执行同样的关系代数表达式可能得到不同结果。本文统一使用这样一组演示数据后面所有运算都会围绕这三张表展开。3. 实验环境与演示数据3.1 环境说明关系代数本身是抽象理论不依赖具体数据库产品。但为了验证“关系代数表达式与 SQL 结果一致”本文会给出可执行的 SQL 示例。这些 SQL 采用标准语法在 MySQL 8.x、PostgreSQL、SQL Server 等主流数据库中都适用。你可以使用本地数据库也可以使用在线的 SQL 练习平台。版本不需要刻意保持一致重点是理解表结构和运算逻辑。如果你的环境不支持某个运算符例如 MySQL 8.0 之前的版本不支持 EXCEPT可以参考文中给出的替代写法。3.2 建表语句下面是三张表的建表 SQL建议直接复制到你的数据库中执行。CREATE TABLE Student ( Sno CHAR(3) PRIMARY KEY, Sname VARCHAR(20), Sex CHAR(2), Age INT, Dept VARCHAR(20) ); CREATE TABLE Course ( Cno CHAR(2) PRIMARY KEY, Cname VARCHAR(50), Credit INT ); CREATE TABLE SC ( Sno CHAR(3), Cno CHAR(2), Score INT, PRIMARY KEY (Sno, Cno) );Course 表存储课程信息SC 表是选课表记录每个学生选修了哪门课以及成绩。Sno 和 Cno 合在一起作为联合主键表示一个学生同一门课只能有一条选课记录。3.3 插入演示数据插入数据的 SQL 如下INSERT INTO Student VALUES (001, 张三, 男, 20, CS), (002, 李四, 女, 19, CS), (003, 王五, 男, 22, MA), (004, 赵六, 女, 21, CS), (005, 钱七, 男, 20, IS), (006, 孙八, 女, 23, MA); INSERT INTO Course VALUES (C1, 数据库原理, 4), (C2, 操作系统, 3), (C3, 计算机网络, 3); INSERT INTO SC VALUES (001, C1, 92), (001, C2, 85), (001, C3, 88), (002, C1, 90), (002, C3, 78), (003, C2, 82), (003, C3, 91), (004, C1, 68), (004, C2, 79), (006, C1, 85), (006, C2, 87);数据说明Student 表有 6 名学生其中 001、002、004 属于计算机系 CS。Course 表有 3 门课程。SC 表记录了选课关系例如 001 同学选修了全部 3 门课程。005 号同学没有选修任何课程这个数据在后面差运算示例中会用到。下面进入全文最核心的部分八个关系代数基础运算。4. 八个基础运算精讲4.1 选择 σ按条件筛选行选择运算记为 σ读作 sigma。它从一个关系中筛选出满足给定谓词条件的元组相当于 SQL 中的 WHERE 子句。定义形式σ_条件(关系)其中条件是由属性名、常量、比较运算符、≠、、、≥、≤和逻辑运算符∧ 与、∨ 或、¬ 非组成的布尔表达式。示例查询计算机系CS的全部学生。σ_DeptCS(Student)计算过程扫描 Student 表中的每一行检查 Dept 属性是否等于 CS保留满足条件的行。结果如下SnoSnameSexAgeDept001张三男20CS002李四女19CS004赵六女21CS对应的 SQLSELECT * FROM Student WHERE Dept CS;选择运算有两个容易出错的地方。第一它返回的关系与原关系有相同的属性结构不会减少列。第二条件中的属性名必须真实存在于被操作的关系中否则在关系代数表达式的合法性检查阶段就会失败。再来一个复合条件示例查询计算机系并且年龄不小于 20 岁的学生。σ_DeptCS ∧ Age 20(Student)结果SnoSnameSexAgeDept001张三男20CS004赵六女21CS对应的 SQLSELECT * FROM Student WHERE Dept CS AND Age 20;多次选择可以写成链式形式例如 σ_DeptCS( σ_Age 20(Student) )但通常会化简为单个复合条件。4.2 投影 π按列筛选字段投影运算记为 π读作 pi。它从关系中选出指定的列并去掉重复行相当于 SQL 中 SELECT 后面跟指定列的效果。定义形式π_属性列表(关系)示例查询所有学生的姓名和所在系。π_Sname, Dept(Student)结果SnameDept张三CS李四CS王五MA赵六CS钱七IS孙八MA这里有个关键特性投影结果中的重复元组会被自动消除。如果查询结果里两行在所有投影属性上完全相同只保留一行。例如查询学生的性别π_Sex(Student)结果只有两行Sex男女虽然 Student 表中有 6 行数据但性别只有两种取值投影得到的关系基数是 2。这一点和 SQL 的默认行为不同SQL 的 SELECT 默认不去重需要显式加 DISTINCT 才会去重。SELECT DISTINCT Sex FROM Student;投影运算的结果列顺序、列名通常与属性列表一致。如果后续还需要使用学生的其他属性则不能只投影一部分列必须重新联系原始关系或使用更完整的投影列表。投影和选择经常组合使用。查询计算机系学生的姓名π_Sname( σ_DeptCS(Student) )对应的 SQLSELECT Sname FROM Student WHERE Dept CS;运算顺序是先选择、再投影。选择负责缩小行范围投影负责缩小列范围二者一横一纵是关系代数里最常用的组合。4.3 并 ∪合并两个兼容关系并运算记为 ∪。它将两个结构兼容的关系合并保留所有元组并去掉重复项相当于 SQL 中的 UNION。两个关系进行并运算的前提是属性数量相同。对应位置属性的域相同或兼容。这种关系称为“并兼容”。实际使用中最稳妥的做法是确保两个关系来自同一张表的同一结构或者都是通过相同属性列表投影得到的临时关系。示例查询年龄小于等于 20 岁的学生并上计算机系的学生。第一个关系 R1 是年龄小于等于 20 岁的学生R1 σ_Age 20(Student)R1 结果SnoSnameSexAgeDept001张三男20CS002李四女19CS005钱七男20IS第二个关系 R2 是计算机系学生R2 σ_DeptCS(Student)R2 结果SnoSnameSexAgeDept001张三男20CS002李四女19CS004赵六女21CS并运算 R1 ∪ R2 的结果SnoSnameSexAgeDept001张三男20CS002李四女19CS005钱七男20IS004赵六女21CS001 和 002 在 R1、R2 中都出现但并结果中只保留一次。注意结果中行的顺序在理论层面没有意义。对应的 SQLSELECT * FROM Student WHERE Age 20 UNION SELECT * FROM Student WHERE Dept CS;如果换成 UNION ALL则会保留重复行对应关系代数中不考虑去重的“多重集并”。标准关系代数默认去重因此与 UNION 更对应。4.4 差 −从一个关系减去另一个关系差运算记为 −。它从第一个关系中删除出现在第二个关系中的元组相当于 SQL 中的 EXCEPTMySQL 8.0 开始支持或 Oracle 中的 MINUS。同样要求两个关系并兼容。示例查询计算机系学生中年龄小于 21 岁的学生以外的其他学生。先求出计算机系学生R1 σ_DeptCS(Student)再求出计算机系中年龄小于 21 岁的学生R2 σ_DeptCS ∧ Age 21(Student)最后计算差R1 − R2R1 是 001、002、004 三个计算机系学生R2 是 001、002 两个符合年龄条件的学生。相减后结果是SnoSnameSexAgeDept004赵六女21CS对应的标准 SQLSELECT * FROM Student WHERE Dept CS EXCEPT SELECT * FROM Student WHERE Dept CS AND Age 21;在 MySQL 8.0 中可以直接运行 EXCEPT在更早版本中可以改写为 NOT IN 或 NOT EXISTSSELECT * FROM Student WHERE Dept CS AND (Sno, Sname, Sex, Age, Dept) NOT IN ( SELECT Sno, Sname, Sex, Age, Dept FROM Student WHERE Dept CS AND Age 21 );差运算经常用于表达“排除”语义。例如查询没有选过任何课程的学生可以先求全体学生的学号再减去选课表中的学号集合π_Sno(Student) − π_Sno(SC)这个表达式在本文综合案例中会再次出现。4.5 笛卡尔积 ×两表所有行组合笛卡尔积记为 ×。它将两个关系的元组逐一配对得到一个新关系。新关系的属性数量是两个关系属性数量之和元组数量是两个关系元组数量之积。形式R × S示例Student6 行 5 列与 Course3 行 3 列做笛卡尔积得到 18 行 8 列。由于结果行数太多这里只展示前几行SnoSnameSexAgeDeptCnoCnameCredit001张三男20CSC1数据库原理4001张三男20CSC2操作系统3001张三男20CSC3计算机网络3002李四女19CSC1数据库原理4002李四女19CSC2操作系统3........................对应的 SQLSELECT * FROM Student CROSS JOIN Course;笛卡尔积的问题很明显它会生成大量无意义的行。比如学生“张三”和“操作系统”组合实际上没有对应选课关系但在笛卡尔积里会出现。因此笛卡尔积通常不会单独用于查询而是作为连接运算的基础。如果直接在 SQL 中写多表连接却不加连接条件SELECT * FROM Student, SC;那得到的就是两个表的笛卡尔积。生产环境中这种写法非常危险可能瞬间产生巨大的数据集。理解笛卡尔积能帮你理解为什么多表连接必须写连接条件。4.6 交 ∩取两个关系的共同元组交运算记为 ∩。它返回同时出现在两个关系中的元组相当于 SQL 中的 INTERSECT。两个关系同样需要并兼容。示例查询既被 001 号学生选修又被 003 号学生选修的课程号。先求 001 选修的课程π_Cno( σ_Sno001(SC) )结果是 C1、C2、C3。再求 003 选修的课程π_Cno( σ_Sno003(SC) )结果是 C2、C3。取交集π_Cno( σ_Sno001(SC) ) ∩ π_Cno( σ_Sno003(SC) )结果CnoC2C3对应的标准 SQLSELECT Cno FROM SC WHERE Sno 001 INTERSECT SELECT Cno FROM SC WHERE Sno 003;在 MySQL 8.0 之前没有 INTERSECT可以用 IN 改写SELECT DISTINCT Cno FROM SC WHERE Sno 001 AND Cno IN (SELECT Cno FROM SC WHERE Sno 003);在关系代数体系中交运算可以由差运算推导出来R ∩ S R − (R − S)这个公式很重要它说明并非所有运算都是基础运算。在理论教学中通常只把选择、投影、并、差、笛卡尔积作为基本运算交、连接、除都可以由它们组合推导。4.7 连接 ⋈按条件配对两张表连接运算记为 ⋈。它本质上是笛卡尔积加选择条件的组合用于把两张表中满足关联条件的行拼在一起。最常见的连接形式是 θ 连接R ⋈_条件 S等价于σ_条件(R × S)示例把 Student 和 SC 按学号相等条件连接。Student ⋈_Student.Sno SC.Sno SC由于属性名 Sno 在两张表中都存在必须用“关系名.属性名”的方式限定避免歧义。结果包含 11 行下表只展示其中几行SnoSnameSexAgeDeptCnoScore001张三男20CSC192001张三男20CSC285001张三男20CSC388002李四女19CSC190004赵六女21CSC168.....................连接条件中的属性名可以不同例如按学生年龄和课程学分比较。对应的 SQLSELECT * FROM Student JOIN SC ON Student.Sno SC.Sno;如果连接条件是同名属性的等值比较并且结果中不保留重复的关联列就称为自然连接记为Student ⋈ SC自然连接会自动找出两张表中同名属性并按照“值相等”进行连接同时只保留一个同名列。自然连接的 SQL 写法是SELECT * FROM Student NATURAL JOIN SC;需要注意的是自然连接要求同名属性的值确实表示同一个含义。比如 Student 中有 AgeCourse 中如果也有 Age 字段但含义是“课程适合年龄”就会被错误连接。因此生产环境更推荐显式指定连接条件而不是滥用手动指定的自然连接。4.8 除 ÷求解“全部包含”关系除运算记为 ÷是八大运算中最抽象、也最容易理解错的一个。它主要用于表达“至少包含所有……”这类语义。定义可以这样理解R ÷ S表示从 R 中找出这样的元组它与 S 中所有元组的组合都出现在 R 中。在选课场景里最经典的问题就是“查询选修了全部课程的学生”。在这里R 是选课关系投影出的学号-课程号对S 是全部课程号。构造被除关系 RR π_Sno, Cno(SC)R 的内容是每个学生选修的课程号。构造除数 SS π_Cno(Course)S 的内容是所有课程号。因为表中有 C1、C2、C3 三门课所以 S 有 3 行。除法 R ÷ S 的计算过程可以拆解为三步第 1 步找出课程表中全部课程{C1, C2, C3}第 2 步检查每个学生选课集合是否包含这 3 门课。001 选了 C1、C2、C3包含全部课程保留。 002 选了 C1、C3缺少 C2排除。 003 选了 C2、C3缺少 C1排除。 004 选了 C1、C2缺少 C3排除。 006 选了 C1、C2缺少 C3排除。第 3 步得到商关系Sno001对应的 SQL 可以使用双重 NOT EXISTS 实现SELECT DISTINCT Sno FROM SC AS SC1 WHERE NOT EXISTS ( SELECT 1 FROM Course WHERE NOT EXISTS ( SELECT 1 FROM SC AS SC2 WHERE SC2.Sno SC1.Sno AND SC2.Cno Course.Cno ) );这段 SQL 的语义是找这样的学生不存在任何一门课是他没有选修过的。读起来有点绕但逻辑和除运算完全一致。除运算还可以表达更复杂的“全部包含”问题。比如查询选修了 001 学生所选全部课程的学生可以把除数改为 001 的选课集合π_Sno, Cno(SC) ÷ π_Cno( σ_Sno001(SC) )001 选了 C1、C2、C3所以结果还是只有 001 自己。5. 综合实例选课业务查询前面八个运算拆开看都很简单真正重要的是组合使用。下面用一组典型的选课查询演示如何用关系代数表达并与 SQL 一一对照。5.1 查询选修了 C2 课程的学生姓名关系代数表达式π_Sname( σ_CnoC2( Student ⋈ SC ) )计算顺序是先自然连接 Student 和 SC得到带学生信息的选课记录再选择课程号为 C2 的行最后投影学生姓名。对应的 SQLSELECT DISTINCT Sname FROM Student JOIN SC ON Student.Sno SC.Sno WHERE SC.Cno C2;结果Sname张三王五赵六孙八5.2 查询同时选修 C1 和 C2 课程的学生学号“同时选修”在关系代数中可以通过交运算表达。π_Sno( σ_CnoC1(SC) ) ∩ π_Sno( σ_CnoC2(SC) )对应的 SQLSELECT Sno FROM SC WHERE Cno C1 INTERSECT SELECT Sno FROM SC WHERE Cno C2;如果不能使用 INTERSECT可以改写为SELECT DISTINCT Sno FROM SC WHERE Cno C1 AND Sno IN (SELECT Sno FROM SC WHERE Cno C2);结果Sno0010040065.3 查询没有选修任何课程的学生没有选修任何课程等价于“学生集合减去有选课记录的学生”。π_Sno(Student) − π_Sno(SC)结果Sno005对应的 SQLSELECT Sno FROM Student EXCEPT SELECT Sno FROM SC;MySQL 8.0 以下可以改写为SELECT Sno FROM Student WHERE Sno NOT IN (SELECT Sno FROM SC);5.4 查询选修了全部课程的学生姓名这里用到除运算。先求出选修全部课程的学生学号再连接 Student 得到姓名。π_Sname( Student ⋈ ( π_Sno, Cno(SC) ÷ π_Cno(Course) ) )对应的 SQLSELECT Sname FROM Student WHERE Sno IN ( SELECT Sno FROM SC AS SC1 WHERE NOT EXISTS ( SELECT 1 FROM Course WHERE NOT EXISTS ( SELECT 1 FROM SC AS SC2 WHERE SC2.Sno SC1.Sno AND SC2.Cno Course.Cno ) ) );最终结果是“张三”。因为整个演示数据中只有 001 号学生选修了全部三门课程。5.5 查询计算机系学生选修了但成绩低于 80 分的课程名这个问题需要连接三张表先选计算机系学生再连接选课表再连接课程表最后筛选成绩。π_Cname( σ_DeptCS ∧ Score 80( Student ⋈ SC ⋈ Course ) )对应的 SQLSELECT DISTINCT Cname FROM Student JOIN SC ON Student.Sno SC.Sno JOIN Course ON Course.Cno SC.Cno WHERE Student.Dept CS AND SC.Score 80;结果Cname数据库原理在这个结果中004 号学生赵六选修了数据库原理成绩 68 分低于 80满足条件。6. 关系代数学习中的常见误区6.1 混淆选择与投影选择筛选的是行投影筛选的是列。很多初学同学写出 σ_Sname张三(Student) 这样看似合理但错误的表达式。Sname 是列名用选择运算筛选时只能写“属性与常量的比较”所以正确的写法是σ_Sname张三(Student)这样是合法的因为条件是“姓名等于张三”。但如果你想“只看姓名这一列”应该用投影π_Sname(Student)两者的区别是选择保留整行投影只保留指定列。6.2 忽略并、差、交运算的兼容性并、差、交运算要求两个关系有相同的属性数量并且对应属性的域兼容。如果把 π_Sname(Student) 和 π_Sno(SC) 做并运算结果就是非法的因为一个是姓名、一个是学号属性数量和语义不一致。6.3 笛卡尔积不加条件笛卡尔积会放大数据量。假设 Student 有 1000 行SC 有 10 万行乘积就是 1 亿行。如果不加选择条件直接用于查询不仅结果没有意义还会导致数据库执行计划崩溃。在 SQL 中写多表连接时务必确认连接条件已写明。6.4 把自然连接与等值连接混淆自然连接基于同名属性自动连接并且只保留一个同名列。等值连接可以基于不同属性名的等值比较并且结果中会保留两张表原有的所有属性列。生产环境中显式写连接条件更安全、更可控。6.5 除运算只能记住结果形式很多同学背下除运算的公式却没理解它的含义。其实只要记住一句话除运算是在问“哪些对象满足与给定集合中每个元素都存在配对关系”。建议做题时先手算两遍再对照 SQL 理解。7. 最佳实践与学习建议7.1 自建老式练习法关系代数的难点在于抽象的集合思维。最有效的练习方式是给出一张很小的表先手写出每次运算的中间结果再用 SQL 验证。比如把第 4 节中的 8 个例子全部手算一遍你会对每个运算的输入输出结构产生直观印象。7.2 表达式书写顺序写复杂关系代数表达式时一般遵循“先窄后宽、先选后连”的原则先用选择缩小数据量再用投影减少列数最后做连接和集合运算。虽然关系代数的等价变换允许调整顺序但良好顺序能降低中间结果规模这种习惯在理解查询优化时非常有价值。7.3 对照 SQL 学习每学一个关系代数运算符就把它与 SQL 对应起来σ 对应 WHEREπ 对应 SELECT 列列表∪ 对应 UNION− 对应 EXCEPT / MINUS× 对应 CROSS JOIN∩ 对应 INTERSECT⋈ 对应 JOIN÷ 对应 NOT EXISTS 嵌套查询。这种对照关系能帮助你快速从理论切换到实际开发。7.4 不要忽略空值与重复元组标准关系代数建立在集合论上默认不存在重复元组也不涉及空值。但真实数据库表允许 NULL 和重复行。因此在 SQL 中验证关系代数表达式时可能会因为 NULL 比较、COUNT 统计方式不同而产生偏差。理解理论模型时以标准关系代数为准写 SQL 时要额外考虑空值和去重。7.5 注意安全边界与生产实践如果要在真实业务库中执行验证查询务必遵守以下原则只对测试库或只读库执行复杂验证涉及UPDATE、DELETE时先开启事务或备份执行大规模CROSS JOIN前先估算数据量生产环境查询优化依赖执行计划不要认为关系代数表达式越短性能就越好。8. 总结与下一步说到这可能你已经发现了关系代数查询并不难八个基础运算各有明确职责。选择管行方向筛选投影管列方向筛选并、差、交管集合方向运算笛卡尔积和连接管表与表之间的组合除则解决“全部包含”这类复杂语义。真正拉开差距的是能否把复杂查询拆解成这些基础运算的组合。本文是关系代数查询的第一部分。建议你把文中的三张表建好把 8 个运算符的例子全部手算一遍再对照 SQL 验证结果。这个“手算 → 写 SQL → 对比结果”的过程能帮你同时打通理论理解和动手能力。下一步可以继续学习三块内容一是等值连接、自然连接、外连接的区别与适用场景二是关系代数表达式的等价变换规则比如选择下推、投影下推三是结合数据库执行计划观察同一查询在不同表达式形式下的执行代价变化。这些内容会在后续文章中继续展开。动手把示例数据建好逐个验证一遍比单纯背定义有用得多。