Day28-06|6.2.2 关系运算:从集合操作理解查询、连接与数据处理

本文对应《系统架构设计师教程(第2版)》第6章“数据库设计基础知识”6.2.2“关系运算”。关系运算建立在关系模型之上,用形式化的集合操作描述如何从一个或多个关系得到新的关系。选择、投影、连接、并、差、笛卡尔积等运算既是关系数据库理论的基础,也是理解 SQL 查询、执行计划、索引设计和数据服务接口的重要入口。系统架构师不一定每天手写关系代数,但必须能够看懂查询逻辑、判断结果是否正确,并分析查询复杂度和数据量增长后的性能风险。

一、学习目标与考试定位

完成本节后,应能够区分关系代数和关系演算的基本思想;掌握选择、投影、并、差、交、笛卡尔积、连接、自然连接、除等运算的含义和结果特征;能够把常见 SQL 语句转换成关系运算思路;能够判断运算前后的元数、基数和属性变化;能够在案例题中分析无条件连接、重复元组、空值、连接条件遗漏和查询性能问题。

软考题目可能给出学生、课程、选课或订单关系,让考生写出满足条件的结果,也可能要求判断某个查询是否等价、哪种运算可以替代连接、为什么笛卡尔积会产生大量结果。答题首先要确认关系是否相容、属性是否同名、连接条件是什么,再说明运算结果和业务含义。

二、关系运算的基本思想

关系运算把关系作为输入,把关系作为输出。结果仍然是一个关系,因此可以继续参与下一步运算,这种可组合性是关系查询强大的原因。运算可以分为传统集合运算和专门关系运算。

传统集合运算包括并、差、交和笛卡尔积;专门关系运算包括选择、投影、连接和除。关系数据库的查询通常是这些运算的组合,优化器会尝试调整运算顺序、下推过滤条件和选择更合适的访问路径。

关系运算与 SQL 不是逐字一一对应。SQL 允许重复行和空值,实际结果受 DISTINCT、NULL 语义、排序和数据库实现影响;经典关系代数通常基于集合,不允许重复元组。因此在考试和工程实践中,既要掌握理论定义,也要注意 SQL 的实际语义。

三、关系的元数与基数

关系的元数(degree)是属性的数量,也就是列数;关系的基数(cardinality)是元组的数量,也就是行数。不同运算对元数和基数的影响不同,是判断题和计算题常用的切入点。

运算 元数变化 基数变化的典型情况
选择 不变 小于或等于原关系
投影 减少或不变 小于或等于原关系,去重后可能减少
并 相同 不超过两关系基数之和
差 与左关系相同 小于或等于左关系
交 与输入相同 小于或等于任一输入
笛卡尔积 两关系元数相加 等于两关系基数相乘
连接 通常为两关系属性合并后去掉重复连接列 通常小于或等于笛卡尔积

选择和投影常被称为一元运算,因为只需要一个关系;并、差、交、笛卡尔积和连接通常是二元运算,需要两个关系。

四、选择运算

选择(selection)从关系中筛选满足条件的元组,记作 σ 条件(R)。它只保留行,不改变关系的属性结构。例如从订单关系中选出金额大于 1000 且状态为已支付的订单。

对应 SQL 通常是 WHERE 条件:

σ status = ‘PAID’ and amount > 1000 (Orders)

选择的几个重要性质如下:选择条件可以组合,多个选择可以交换或合并;选择可以尽早执行,以减少后续连接和投影处理的数据量;选择条件若涉及索引列,可能使用索引降低扫描成本。优化器中的谓词下推,就是把过滤条件尽可能下推到数据源附近。

选择不改变元数,但可能减少基数。如果条件没有匹配元组,结果是空关系;如果所有元组都满足,结果与原关系相同。条件中的空值要特别注意,SQL 的判断可能得到 UNKNOWN,而不是 TRUE。

五、投影运算

投影(projection)从关系中选出指定属性,记作 π 属性列表(R)。它只保留列,不保留未指定的属性。经典关系代数会自动消除重复元组,SQL 则只有使用 DISTINCT 才明确要求去重。

例如从客户关系中只取得客户编号和客户等级:

π customer_id, level (Customer)

投影可能减少元数,也可能因选择了全部属性而保持不变。由于投影会丢失未选择的属性,后续若还需要这些属性,必须在投影前保留,不能指望从结果中恢复。工程中“只返回需要的字段”可以减少网络和序列化成本,但过早投影也可能使后续连接缺少连接键。

六、并、差与交

1. 并

并(union)得到属于关系 R 或关系 S 的所有元组,记作 R ∪ S。两个关系必须并相容:元数相同,对应属性的域相容,属性顺序和语义能够对应。经典集合运算会去除重复元组。

并对应 SQL 的 UNION;UNION ALL 保留重复结果,性能通常更好,但业务上可能产生重复数据。并适合合并相同结构的历史表、多个来源的同类数据或不同条件筛选的结果。

2. 差

差(difference)得到属于 R 但不属于 S 的元组,记作 R − S。差不是对称的,R − S 与 S − R 通常不同。它也要求两个关系并相容。

差常用于查找未完成事项,例如“已报名但未缴费的学生”“存在于客户主表但尚未同步到服务表的客户”。SQL 中通常使用 EXCEPT、NOT EXISTS 或反连接表达。

3. 交

交(intersection)得到同时属于 R 和 S 的元组,记作 R ∩ S。它可以用差表达:R ∩ S = R − (R − S)。SQL 中对应 INTERSECT,也可以用连接或存在性条件实现。交适合查找两个集合的共同成员,如同时满足两类资格的用户。

七、笛卡尔积

笛卡尔积(Cartesian product)把 R 中每个元组与 S 中每个元组组合,记作 R × S。若 R 有 m 个元组、S 有 n 个元组,结果基数为 m×n,元数为两者元数之和。

笛卡尔积本身不要求存在业务关联,因此结果可能非常庞大。连接可以理解为先做笛卡尔积,再用条件选择过滤:R 与 S 按条件 θ 连接,可以表示为 σ 条件(R × S)。这说明为什么遗漏连接条件会产生笛卡尔积,并可能造成查询爆炸。

在分析场景中,笛卡尔积有时用于生成日期与门店的组合、枚举可能性或构造测试数据,但必须有明确的规模控制。在线业务查询中出现非预期笛卡尔积,通常是严重的性能和正确性风险。

八、连接运算

连接(join)从两个关系中组合满足连接条件的元组,是关系查询最常用的运算之一。连接通常先依据键、外键或其他条件匹配,再合并属性。

1. θ 连接

θ 连接允许使用 =、<、>、≤、≥、≠ 等比较条件。例如订单金额与优惠门槛之间可以使用大于等于连接。等值连接是 θ 连接的常见特例。

2. 等值连接

等值连接使用等号匹配两个关系的属性,如 Orders.customer_id = Customers.customer_id。结果通常会同时保留两边的连接属性,因此可能出现两个语义相同的列。

3. 自然连接

自然连接自动根据两个关系中同名属性进行等值匹配,并在结果中去掉重复的连接属性。自然连接使用方便,但同名字段只要语义不一致就可能产生错误匹配,因此工程 SQL 更推荐明确写出连接条件和字段来源。

4. 外连接

外连接保留一侧或两侧未匹配的元组,分为左外连接、右外连接和全外连接。左外连接常用于查找没有关联记录的对象,例如查询所有客户及其最近订单,即使客户没有订单也要显示。未匹配一侧的字段通常以 NULL 表示,后续条件必须考虑空值语义。

5. 半连接与反连接

半连接只返回一侧中存在匹配的元组,不需要把另一侧属性全部带回,常用于 EXISTS;反连接返回不存在匹配的元组,常用于 NOT EXISTS 或 NOT IN。如果另一侧数据量大而只需要判断存在性,半连接可能比完整连接更合适。

九、除运算

除运算用于表达“与另一个关系中的所有值都关联”的查询。若 R(X,Y) 除以 S(Y),结果是满足对 S 中每个 Y 值都存在对应 R 元组的 X 值。

典型问题是:“找出选修了某课程集合中全部课程的学生”“找出覆盖全部目标地区的供应商”。除运算可以用分组计数、双重 NOT EXISTS 或反连接组合实现。理解除运算的关键是“全部”,它与“任意一个”“至少一个”不同。

十、运算组合与查询优化

复杂查询往往是选择、投影和连接的组合。优化时要遵循减少中间结果、尽早过滤、保留必要连接键、选择合适连接顺序和利用索引等原则。

例如查询“华东地区已支付订单的客户姓名”,可以先对客户筛选地区,再对订单筛选状态,最后按客户编号连接,并只投影姓名。若先把所有客户和所有订单做笛卡尔积,再过滤,不仅浪费资源,还可能造成内存和网络压力。

选择下推通常可以减少连接输入;投影下推可以减少列宽和网络传输,但要保留后续连接和过滤所需的属性;连接重排可以选择更小或更具选择性的中间关系。优化器会结合统计信息和代价模型选择执行计划,但架构师仍要保证查询表达清晰、索引与访问模式匹配。

十一、关系运算与 SQL 的差异

关系代数通常以集合为基础,结果不重复且没有固定顺序;SQL 默认可能保留重复行,结果顺序只有明确 ORDER BY 才有保证。关系代数中的逻辑相等与 SQL 中的 NULL 三值逻辑也不同。NOT IN 遇到 NULL 可能得不到预期结果,工程上常用 NOT EXISTS 并显式处理空值。

此外,SQL 包含聚合、分组、窗口函数、递归查询、排序和更新等扩展能力,不能全部用基础关系代数直接表达。考试遇到基础运算时按定义作答,工程实践中则要继续考虑优化器、索引、数据分布、事务和执行环境。

十二、案例分析:订单查询遗漏连接条件

某报表接口需要返回订单编号、客户名称和商品名称。开发人员编写查询时连接了订单表、客户表和商品表,但遗漏了订单明细与商品之间的连接条件。测试数据较少时没有明显异常,生产数据量增长后接口响应从几百毫秒升到几十秒,返回结果还出现大量重复组合。

问题分析

遗漏连接条件使多个关系形成非预期笛卡尔积。结果基数接近不同表基数的乘积,既造成性能灾难,也使报表事实重复。查询虽然“有数据返回”,但不能说明结果正确。

改进方案

明确关系链:订单通过客户编号连接客户,订单通过订单明细连接商品;每个连接条件写出双方字段和业务含义,避免依赖同名字段的隐式自然连接。使用执行计划和代表性数据检查连接顺序、估算基数和索引使用;只投影必要字段,并对订单明细的业务粒度做确认,不能用 DISTINCT 粗暴掩盖重复。上线前增加结果行数、金额汇总和样例对账测试,防止性能和正确性问题同时进入生产。

十三、综合知识练习与详细答案

练习一

关系 R 有 100 行、5 个属性,关系 S 有 20 行、3 个属性。求 R×S 的元数和基数上限,并说明选择运算的影响。

参考答案:R×S 的元数为 5+3=8,基数为 100×20=2000。对笛卡尔积结果进行选择,元数仍为 8,基数小于等于 2000;如果选择条件全部不满足则为 0,全部满足则仍为 2000。

练习二

“查询选修了全部必修课程的学生”更接近哪种关系运算?如何用 SQL 思路实现?

参考答案:这是除运算,因为要求学生与必修课程集合中的每个课程都有对应关系。SQL 可以按学生分组,统计其选修的必修课程数是否等于必修课程总数;也可以使用双重 NOT EXISTS,表示不存在一门必修课没有被该学生选修。要注意重复选课记录和学生已退课状态。

练习三

为什么查询“所有客户及其订单”常用左外连接,而不是内连接?

参考答案:内连接只保留存在订单的客户,会丢失没有订单的客户;左外连接保留左侧客户关系中的全部元组,没有订单的客户在订单字段中以 NULL 表示。使用外连接后还要注意过滤条件放在 ON 还是 WHERE 中,条件位置不当可能把外连接又变成内连接。

十四、论文写作素材

论文中可以这样描述查询和数据服务设计:我根据业务查询场景将关系运算拆分为选择、投影和连接,先过滤高选择性条件,再保留必要的连接键和返回字段,减少中间结果、网络传输和内存占用;对客户、订单、明细和商品等关系明确主外键和业务粒度,避免遗漏连接条件造成笛卡尔积和重复统计。

对“存在”“不存在”和“全部满足”类查询,分别采用半连接、反连接和除运算对应的 SQL 表达,并结合空值、重复数据和事务状态进行验证。通过执行计划、慢查询、基数估算、索引命中率和代表性数据进行性能分析,不能用 DISTINCT 掩盖连接错误。对报表与在线交易分离负载,必要时使用查询投影、预聚合、分析库或缓存,并维护数据同步、对账和延迟监控。

十五、今日复盘

关系运算的核心是把查询看成可组合的集合变换:选择筛选行,投影筛选列,并合并相容关系,差找出左侧独有,交找共同集合,笛卡尔积生成所有组合,连接按条件匹配关系,除表达“全部满足”。选择和投影的下推可以减少中间结果,连接条件遗漏则可能造成笛卡尔积。

记忆主线:

先选择减少行,再投影减少列;需要关联就连接,需要全集条件就除;所有运算都要检查属性相容、连接条件、重复和空值。

下一篇进入 6.2.3 关系数据库设计基本理论,学习函数依赖、范式、规范化和反规范化的设计权衡。