免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

关系代数查询基础:从选择投影到连接,读懂SQL背后的查询语言

关系代数查询基础:从选择投影到连接,读懂SQL背后的查询语言 关系代数查询是数据库管理系统这门课里最容易被低估的内容。很多人以为它就是一套考前突击的符号规则学完 σ、π、⋈ 就丢到一边实际工作里写 SQL 根本用不上。我工作这些年发现正好相反能不能看懂关系代数表达式直接决定了你能不能读懂数据库的执行计划能不能理解一条慢查询为什么慢以及优化器为什么敢把一个 JOIN 改写成另一种连接顺序。关系代数就是 SQL 背后那套底层的查询语言理解了它你看到的就不再只是表和行而是关系、元组和操作符。这篇文章对应的是关系代数查询的第一部分主要讲基础操作选择、投影、改名、并、差、笛卡尔积以及如何用这些基础操作组合出交和连接。适合正在学数据库管理系统原理的同学也适合想补执行计划基础知识的后端开发。我的建议是学的时候不要只看符号定义要拿一张真实的小表一个操作一个操作推演输出结果一直推到能用自己的话解释为什么这个表达式能查出这条数据为止。先说明一点关系代数操作的输入和输出都是关系。这个性质很重要它意味着任何操作的输出还能继续作为下一步操作的输入所以表达式可以一层套一层复杂查询也因此能被拆成若干基础操作的组合。1. 关系代数不是一门符号课它是查询优化器的底层语言1.1 关系代数到底在做什么关系代数Relational Algebra是一种过程式查询语言。这里过程式是关键你写 SQL 时只声明要什么数据数据库自己决定怎么拿你写关系代数时必须自己把先做哪一步、再做哪一步全部表达出来。比如查计算机系年龄大于 20 的学生SQL 写出来是一个 WHERE 条件关系代数则是先做选择 σ再做投影 π顺序直接写在表达式里。这套操作定义在关系模型之上。关系Relation对应日常说的表元组Tuple对应行属性Attribute对应列。每次操作接收一个或两个关系作为输入产出一个新的关系。这个闭包特性让关系代数可以嵌套投影的结果可以继续做选择选择的结果可以再做连接最终得到一个完整的查询表达式。数据库管理系统在执行一条 SQL 时会把它解析成关系代数表达式树或者等价形式再做逻辑优化和物理优化。这意味着你在学习慢查询为什么慢、看 EXPLAIN 输出、理解索引为什么能用上或不能用上的时候其实都在和关系代数的各种等价改写打交道。没有这层基础执行计划对你来说就只是一个黑盒。1.2 开始之前先建立三个认知第一个认知是术语转换。以后看到关系大脑里要知道这是表看到元组要知道这是一行记录看到属性要知道这是一列字段。这个转换熟练之后再读教材里的表达式就不会被术语卡住。第二个认知是关系代数的集合语义。理论上关系是集合集合里不会出现重复元素所以投影操作会隐含去重。注意这和 SQL 的表不一样SQL 表默认是多重集合允许重复行。很多初学者在这里迷糊为什么关系代数投影结果没有重复行而 SQL 里 SELECT 某一列还会出现重复因为 SQL 为了性能默认保留重复必须加 DISTINCT 才接近投影的效果。这个差异很关键考试和实际读执行计划时都会碰到。第三个认知是操作的组合性。任何一个操作的结果仍然是关系所以表达式可以树状嵌套。学习时不要背十几个独立的公式而是理解清楚每个操作改变什么、保留什么然后像搭积木一样组合。2. 先跑通三个最基础的操作选择、投影、改名2.1 选择σ按条件过滤行选择操作符写作 σ条件(关系)作用是从关系里挑出满足条件的元组。它改变的是行数不改变列数。比如有一张学生表students(sid, name, age, major)关系代数写法σ age 20 (students)意思是取出年龄大于 20 的学生行。结果仍然有四列只是行数变少了。条件也可以组合比如σ age 20 ∧ major 计算机 (students)逻辑与用 ∧ 表示。我一般会让学生先练选择因为它最容易验证拿一张 10 行左右的小表手算条件再对照结果。这里最容易踩的坑是条件里写了表里不存在的属性或者把字符串比较和数值比较混在一起。还要注意条件是作用在原始关系的属性上的。如果前面已经做过投影把某些列去掉了这里就不能再引用那些列。2.2 投影π按列取出需要的属性投影操作符写作 π属性列表(关系)作用是选出若干列去掉其余列。它改变的是列数行数可能也会减少因为集合语义下去重会让重复行合并。π name, major (students)结果只有 name 和 major 两列。如果两个学生同名且同专业理论结果里只会出现一行。这一点和 SQL 行为不同SQL 要写出 SELECT DISTINCT name, major 才能得到同样效果。选择是横向过滤投影是纵向裁剪这两者配合是几乎所有查询的第一步。动手练习时反复确认两个性质σ 不能增加列π 不能过滤行。把这两个性质记牢后面很多判断都会快很多。建议写表达式之前先问自己一句这个操作是要减少行还是要减少列目标和操作符对不上表达式一定写错了。2.3 改名ρ解决重名和自连接问题改名操作符 ρ新名称(关系)或者 ρ新属性1, 新属性2, ...(关系)可以给关系换名字也可以给属性换名字。初学者往往觉得改名不重要实际上它是后面学连接、学自连接时绕不开的操作。举一个典型例子员工表 employee(emp_id, name, manager_id)要查每个员工的上级叫什么名字。你需要把 employee 和它自己连接让员工的 manager_id 去匹配另一个员工副本的 emp_id。如果两个副本都还叫 employee、列都还叫 emp_id连接条件里就不知道哪个 emp_id 是哪个角色。这时就要用改名ρ e1 (employee), ρ e2 (employee)把同一张表变成逻辑上的两张表再做连接条件才能写清楚。改名操作本身不产生新数据它只是给后续表达式提供名字空间但这个动作在复杂查询里非常常见。我见过不少人在做自连接题目时卡住最后发现不是不理解连接而是忘了给其中一个副本改名。3. 集合操作并、差、笛卡尔积3.1 并∪和差−要求表结构兼容并操作是把两个关系的元组合并到一个结果里差操作是从第一个关系里去掉第二个关系里也存在的元组。它们有一个硬性前置条件两个关系必须兼容也就是属性数量相同且对应位置的属性来自同一个域。简单说列数一样每一列的数据类型和语义要对得上。比如有两张表cs_students(sid, name) math_students(sid, name)计算机系学生加上数学系学生cs_students ∪ math_students只在计算机系名单里、但不在数学系名单里的学生cs_students − math_students兼容性检查经常被忽略。我在看课程作业时发现最常见的错误就是拿两个列数不同、或者第二列一个是姓名一个是年龄的表去做并和差结果完全没意义。还有一点差操作的输出一定是从第一个关系里选出来的所以它不会产生第一个关系里没有的元组。3.2 笛卡尔积×每一行都要和另一张表的每一行组合两个关系做笛卡尔积结果是所有元组的组合。如果第一个关系有 m 行 n 列第二个有 k 行 l 列结果就是 m×k 行nl 列。行数和列数都会迅速膨胀所以现实生产环境里几乎不会直接使用纯笛卡尔积但它是一切连接操作的地基。students × courses结果里每一行都是一个学生和一个课程的配对。单独看这个结果大部分是无意义的组合因为没有任何条件约束学生和课程的匹配关系。连接操作JOIN本质上就是先做笛卡尔积再用连接条件做选择的组合。理解了这一步你就知道为什么没有连接条件的多表查询会产生大量无关数据也能理解为什么数据库优化器那么重视连接顺序。纯笛卡尔积在实际查询里很危险结果行数会按两个表的行数相乘增长。要不要写连接条件不是风格问题是正确性和性能问题。3.3 做复合表达式时注意操作顺序集合操作可以组合。比如π name (σ major 计算机 (students)) − π name (σ age 20 (students))这个表达式的意思是先筛出计算机系学生的姓名再筛出年龄小于 20 的学生的姓名最后取差集得到计算机系里年龄不小于 20 的学生姓名。写复合表达式时运算顺序决定结果。括号要写清楚不要靠记忆优先级因为不同教材在交集、差集这类操作上的约定不完全一致。我自己的习惯是从最内层开始读先找最里面的括号确定它输出什么关系再往外一层推。这个方法对考试和阅读论文里的表达式都很管用。4. 派生操作交和连接其实不用单独背4.1 交集∩可以用差集推导关系代数里有些操作是基础操作有些是派生操作。交集就是一个典型派生操作定义是R ∩ S R − (R − S)也就是说两个关系的交集等于R 减去 R 里那些也属于 S 的部分。理解这个推导比死记交集符号有意义得多。只要保证 R 和 S 兼容就能用并、差两个操作组合出交。这也回答了一个常见疑问为什么教材里有时说基础操作只有六个有时又说有八个因为选择、投影、并、差、笛卡尔积、改名是基础操作交、连接都可以由它们推导出来。如果考试里问哪些是基本操作你要能说清这个区分。4.2 θ 连接和自然连接是怎么组合出来的连接操作可以分成几类。θ 连接是这样定义的R ⋈θ S σ θ (R × S)先做笛卡尔积再用连接条件 θ 做选择。如果 θ 是相等条件就是等值连接等值连接里如果结果去掉了重复的公共属性列就是自然连接。自然连接还有一个要求两张表里同名的属性要取值相等然后合并成一个公共列。写成关系代数就是π 所需属性 (σ R公共属性 S公共属性 (R × S))所以自然连接是笛卡尔积 → 等值选择 → 去掉重复列 → 按需投影一串组合操作。我建议你手动推一遍拿两个只有三四行的小关系按这四步逐步展开比看十遍定义都有效。4.3 为什么连接是实际查询里最重要的操作真实业务查询很少只查一张表多张表之间的关联基本都靠连接。连接最消耗资源也最容易出问题没有连接条件的笛卡尔积会让结果指数膨胀连接列上没索引会让数据库慢到不可接受连接顺序选错会让中间结果变得巨大。这些问题的根源都在关系代数的组合方式里。学习第一部分时只要能把连接拆成笛卡尔积 选择 投影 改名这四个基础操作就已经掌握了核心。后面的自然连接、外连接、半连接都是在这些基础上的扩展到时候再学新符号会轻松很多。5. 表达式写完怎么验证写得对不对5.1 第一步检查表结构兼容性验证一个关系代数表达式不要直接对着计算机跑先从结构下手。先看每个操作的输入关系列数、列名和值域再看输出是什么结构。尤其是并、差、交三个操作的输入必须兼容选择的条件下引用的属性必须存在于输入关系里投影的列名也必须存在于输入关系里。举个例子σ major 计算机 (students) 能不能写能前提是 students 确实有 major 列。如果你前面写了 π name (students)后面又写 σ major 计算机就直接无效因为投影结果里已经没有 major 列了。这类错误在纸面推导里特别常见。5.2 第二步拿小数据逐行推演结构检查通过后拿一张只有四五行的小表手写一遍操作过程。比如选择把满足条件的行标出来投影把不需要的列划掉笛卡尔积按行两两组合。推演完对比预期结果再决定下一步。我自己的习惯是先给自己提三个问题结果的行数大概是多少结果的列数大概是多少有没有哪一行数据明显不符合条件。三个问题都能答出来才说明这个表达式是真的理解了。如果行数对不上不要急着看下面的内容先把操作定义再读一遍。5.3 第三步对照常见错误清单检查选择条件里出现不存在的属性。这是最常见的结构错误。投影提前删掉了后面还要用的列。推演复合表达式时尤其容易犯。做并、差、交时两个关系列数不一致或列语义不匹配。哪怕都是两列一个装姓名一个装年龄也不兼容。笛卡尔积没有配合连接条件导致结果行数远大于预期。自连接不加改名导致连接条件里两个 emp_id 分不清。混淆集合语义和 SQL 的多重集合语义。关系代数默认去重SQL 默认不去重不要拿 SQL 的直觉直接套关系代数。括号顺序不对导致整体语义改变。表达式一长每一步的输出结构都要单独确认。这个清单不是考试技巧是实际读执行计划、排查查询逻辑问题时同样要用的思维顺序。6. 把关系代数和 SQL 对应起来才算真正落地6.1 一张表看懂对应关系关系代数写起来像数学但它的每个操作都能映射到 SQL 的某个子句或关键字关系代数操作作用SQL 对应σ选择行WHEREπ投影列SELECT DISTINCT∪并UNION−差EXCEPT / NOT IN×笛卡尔积CROSS JOIN⋈连接JOIN ... ONρ改名AS / 表别名这张表建议自己动手默写一遍不要只看。理解映射之后再看 EXPLAIN 输出你会发现数据库执行计划里出现的就是扫描、过滤、哈希连接、嵌套循环连接这些物理实现而它们背后对应的正是关系代数里的选择、投影和连接。6.2 为什么优化器能做等价改写关系代数有价值还有一个重要原因它允许等价变换。比如选择下推在某些条件下σ condition (R ⋈ S) 等价于 (σ on R) ⋈ S也就是把过滤条件尽量提前先缩小关系再连接让中间结果变小。这是数据库查询优化最基础的手段之一。读数据库系统原理时会经常看到启发式优化逻辑优化这些词本质上都是在关系代数表达树上做等价改写。这一点平时写 SQL 也有实际意义。当你发现一个 JOIN 查询很慢而某些过滤条件其实可以先作用于单独一张表时你就是在手动做选择下推。理解了关系代数就不会觉得优化器的行为是玄学。6.3 第一部分学完后接下来学什么关系代数查询通常会被拆成几个部分。第二部分往往会讲更复杂的连接类型外连接、半连接、除操作以及聚合和分组对应的扩展关系代数。第一部分的基础操作是这些内容的地基建议先把选择、投影、改名、并、差、笛卡尔积练熟再用它们推导交集和连接。我的建议学习路径是用小表手动推演每个操作 → 把表达式翻译成 SQL 对照结果 → 随意组合两个以上操作写复合表达式 → 再去看数据库执行计划验证自己的直觉。每一步都在前面检查过后面再学新操作就不会觉得符号满天飞。最后留一个我个人很受用的经验关系代数适合用来把问题想清楚SQL 适合用来把问题跑出来。学第一部分的阶段不要急着背公式也不要急着刷题真正值得投入时间的是拿一张真实小表把一个复合表达式一层一层剥开直到每层输出的行列你都心里有数。踩过几次明明 SQL 能查出来手写关系代数却写不出的坑之后你会发现问题很多不是知识不够而是没有把选择、投影、连接这些基础操作真正当成一种思维方式。
返回列表