关系代数是数据库系统的理论基础,它提供了一套形式化的操作集合,用于描述和操作关系数据库中的数据。理解关系代数对于掌握数据库查询原理至关重要,特别是在编写复杂SQL查询时,关系代数思维能帮助我们更清晰地构建查询逻辑。
关系代数的核心在于"关系"这一概念。在数据库语境中,关系可以简单理解为一张二维表,由行(元组)和列(属性)组成。与传统代数中的加减乘除类似,关系代数也定义了一系列操作符,但操作对象和结果都是关系(表)。
注意:关系代数与SQL有对应关系,但并非一一对应。SQL是具体的查询语言实现,而关系代数是理论基础。理解这种对应关系能帮助我们写出更优化的SQL查询。
关系代数操作主要分为两类:
在实际数据库查询中,我们通常会组合使用这些操作来构建复杂查询。下面这个简单的例子展示了如何将自然语言查询转化为关系代数表达式:
"查找计算机系的所有学生"
→ πSno,Sname(σSD='计算机系'(S))
这个表达式先对学生表S进行选择操作(σ),筛选出SD(系别)为"计算机系"的元组,然后投影(π)出学号(Sno)和姓名(Sname)两列。
选择操作符σ用于从关系中筛选满足特定条件的元组(行)。其语法形式为:
σ<选择条件>(关系名)
选择条件是一个逻辑表达式,可以使用比较运算符(=, ≠, >, <, ≥, ≤)和逻辑运算符(∧与, ∨或, ¬非)来构建。
典型应用场景:
实操技巧:在选择条件中,字段名通常不加引号,而字符串常量需要用单引号括起来。例如:σSname='张三'(S)
选择操作的一个重要特性是它不会改变关系的结构(即不改变属性/列),只是过滤行。这与投影操作形成对比。
投影操作符π用于从关系中选择特定的属性(列)。其语法形式为:
π<属性列表>(关系名)
关键特点:
应用示例:
πSno,Sname(S) - 从学生表中提取学号和姓名两列
πCno,Cname(C) - 从课程表中提取课程编号和课程名称
常见问题:投影后可能会出现重复元组被自动去除的情况。例如,如果只投影系别πSD(S),结果中每个系只会出现一次,这与SQL中的DISTINCT效果相同。
投影操作经常与选择操作结合使用,形成查询的基本结构:先选择需要的行,再投影需要的列。
自然连接是一种特殊的等值连接,它会自动基于两个关系中同名的属性进行等值比较,并在结果中合并相同的属性。
主要特点:
典型应用:
S⋈SC - 学生表与选课表基于Sno的自然连接
SC⋈C - 选课表与课程表基于Cno的自然连接
自然连接的一个强大之处在于可以链式连接多个表:
S⋈SC⋈C - 连接学生、选课和课程三张表
注意事项:自然连接依赖于同名属性,如果两个表中连接条件的列名不同,需要先使用重命名操作(ρ)统一列名,或者改用θ连接(带有显式连接条件的连接)。
除法操作是关系代数中最难理解的操作之一,用于解决"全部满足"类型的查询问题。其语法形式为:
R ÷ S
其中,S的属性集必须是R的属性集的子集。结果的属性集是R中不在S中的那些属性。
语义解释:R ÷ S的结果包含所有与S中每个元组都能组合出现在R中的元组。
典型应用场景:
例如,查询选修了所有课程的学生:
πSno,Cno(SC) ÷ πCno(C)
这个表达式先从选课表中投影学号和课程号,然后除以课程表中投影的课程号,结果就是那些选修了课程表中所有课程的学生的学号。
差操作用于找出存在于第一个关系但不存在于第二个关系中的元组。其语法形式为:
R − S
关键要求:
应用场景:
例如,查找未选修2号课程的学生姓名和系别:
πSname,SD(S) − πSname,SD(σCno='2'(S⋈SC))
这个表达式先找出所有学生的姓名和系别,然后减去选修了2号课程的学生的姓名和系别,得到的就是未选修该课程的学生。
重要提示:差操作要求两个操作数的模式(属性结构)完全相同。在实际应用中,可能需要通过投影来调整属性结构以满足这一要求。
实际数据库查询往往需要组合多个关系代数操作。构建这类表达式时,需要遵循一定的逻辑顺序,通常从最内层的操作开始逐步向外构建。
构建策略:
以"检索选修了'数据库'课程的学生的学号、姓名及成绩"为例:
πSno,Sname,Grade(σCname='数据库'(S⋈SC⋈C))
构建过程:
查询包含"或"条件时,需要在选择条件中使用逻辑或(∨)连接多个条件。
示例:检索选修了"操作系统"或"数据库"课程的学生的学号和姓名
πSno,Sname(σCname='操作系统'∨Cname='数据库'(S⋈SC⋈C))
性能提示:这类查询在转换为SQL时,通常会被优化为IN语句:WHERE Cname IN ('操作系统', '数据库')
关系代数中没有直接的区间表示法,范围查询需要拆分为两个条件用逻辑与(∧)连接。
示例:检索年龄在18~20之间的女生的学号、姓名及年龄
πSno,Sname,Age(σSex='女'∧Age≥18∧Age≤20(S))
查询"不满足某条件"的记录时,可以使用差操作或选择条件中的逻辑非(¬)。
示例:检索不修2号课程的学生的姓名和所在系
πSname,SD(S) − πSname,SD(σCno='2'(S⋈SC))
或者使用否定条件:
πSname,SD(σ¬(Sno ∈ πSno(σCno='2'(SC)))(S))
这类查询是除法操作的典型应用场景。
示例:检索选修了全部课程的学生的姓名及所在系
πSname,SD(S⋈(πSno,Cno(SC) ÷ πCno(C)))
这个查询的逻辑是:
理解关系代数与SQL的对应关系有助于编写更优化的SQL查询。以下是主要操作符的SQL对应:
| 关系代数 | SQL等效 |
|---|---|
| σ<条件>(R) | SELECT * FROM R WHERE <条件> |
| π<列列表>(R) | SELECT <列列表> FROM R |
| R ⋈ S | SELECT * FROM R NATURAL JOIN S |
| R × S | SELECT * FROM R CROSS JOIN S |
| R ∪ S | SELECT * FROM R UNION SELECT * FROM S |
| R − S | SELECT * FROM R EXCEPT SELECT * FROM S |
| R ÷ S | 需要复杂子查询实现 |
以之前的复杂示例为例,关系代数表达式:
πSno,Sname,Grade(σCname='数据库'(S⋈SC⋈C))
对应的SQL可能是:
SELECT s.Sno, s.Sname, sc.Grade
FROM S s NATURAL JOIN SC sc NATURAL JOIN C c
WHERE c.Cname = '数据库';
注意:NATURAL JOIN在实际中较少使用,更常见的是显式指定连接条件的JOIN语法:
SELECT s.Sno, s.Sname, sc.Grade
FROM S s JOIN SC sc ON s.Sno = sc.Sno
JOIN C c ON sc.Cno = c.Cno
WHERE c.Cname = '数据库';
编写关系代数表达式时,可以考虑以下优化原则:
尽早执行选择操作:将选择条件尽可能向内层移动,减少中间结果的规模
合理排序连接操作:先连接较小的关系或选择性高的关系
投影尽早执行:尽早去除不需要的列,减少数据量
利用等价变换规则:
属性名不匹配:
差操作属性不一致:
除法操作属性不匹配:
自然连接的同名属性问题:
由于除法操作理解难度大且不是所有数据库都直接支持,在实际SQL中常用替代方案:
sql复制SELECT s.Sno
FROM S s
WHERE NOT EXISTS (
SELECT c.Cno FROM C c
WHERE NOT EXISTS (
SELECT * FROM SC sc
WHERE sc.Sno = s.Sno AND sc.Cno = c.Cno
)
)
sql复制SELECT sc.Sno
FROM SC sc
GROUP BY sc.Sno
HAVING COUNT(DISTINCT sc.Cno) = (SELECT COUNT(*) FROM C)
性能考虑:对于大型数据库,第二种方法通常性能更好,特别是当相关列有索引时。
数据库查询优化器通常会先将SQL转换为关系代数表达式,然后应用各种等价变换规则来寻找更优的执行计划。理解这一过程有助于我们编写更优化的SQL。
例如,优化器可能会:
作为数据库开发人员,我们可以利用这些知识来: