卷 I · 起底CH 03深度 03/24

关系,就是一个集合

「关系数据库」这个词你天天说,但「关系」到底指什么?不是表和表之间的外键关系——那是个美丽的误会。「关系」(relation)是数学词,它就是一张表本身,一个元组的集合。想通这一点,SQL 的很多设计会突然变得理所当然。

关系模型元组集合关系代数

「关系」不是「表之间的关系」

先纠正一个几乎人人都有的误解。「关系数据库」的「关系」,不是指「订单表和用户表通过 user_id 关联起来」那种关系。它是个严格的数学概念,来自集合论:

  • 一个关系(relation),就是一张
  • 表里的一,叫一个元组(tuple)——PostgreSQL 的源码和文档里,「tuple」这个词随处可见,你现在知道它就是「行」。
  • 表的一,叫一个属性(attribute),它有名字、有类型(叫「域」,domain)。

这套术语是 E.F. Codd 在 1970 年的论文里立下的。他的洞见是:把数据组织成「关系」(也就是规整的二维表),就能用一套严格的数学——关系代数——去操作它。你不用关心数据在磁盘上怎么摆、用什么指针串起来,你只在「集合」这个抽象层面上说话。这套抽象,就是 SQL 的地基。

◆ 一个关系 = 一个集合

关键的一步是:一个关系,是它那些元组组成的「集合」。集合有两条数学性质,深深烙进了 SQL 的设计:

  • 无序:集合里的元素没有先后。所以表里的行本质上是没有顺序的——你不写 ORDER BY,数据库就没义务按任何特定顺序还你(哪怕它「看起来」总是按插入顺序,那也只是当前实现的巧合,不能依赖)。
  • 不重复:集合里没有两个相同的元素。所以纯粹的关系里不该有两条完全一样的行——这正是 DISTINCTUNION 默认去重的根源。

SQL 的每一句,都是在集合上做运算

既然表是集合,那查询就是集合运算。关系代数定义了一小把基本算子,SQL 里那些关键字,几乎都能对回其中一个。最核心的两个:

  • 选择(σ,sigma):从一个关系里筛出满足条件的行,扔掉不满足的。这就是 SQL 的 WHERE
  • 投影(π,pi):从一个关系里只留下指定的列,砍掉其余的。这就是 SQL 的 SELECT 列名

于是一句最普通的查询:

SELECT title, price       -- π 投影:只要这两列
FROM books
WHERE genre = 'scifi';    -- σ 选择:只要这些行

翻译成关系代数就是 πtitle, price( σgenre='scifi'( books ) )——先在 books 上做选择筛出科幻书,再在结果上做投影只留标题和价格。一层套一层。

而这「一层套一层」为什么成立?因为关系运算有个漂亮的性质叫闭包(closure,此处指代数意义上的封闭性,不是编程里的那个闭包):关系运算的输入是关系,输出还是关系。σ 作用在一张表上,得到的还是一张表;π 再作用上去,得到的仍是一张表。正因如此,你才能把 WHEREJOIN、子查询无限嵌套下去——每一步的产物,都是下一步合法的输入。这是 SQL 能这么强的根本原因。

▶ 动手 · 在一张表上叠算子

下面这台 demo 把 σ 和 π 摊开给你。选一个筛选条件(σ)、选要投影哪些列(π),看它们怎么一层层作用在 books 上,结果始终是一张表。注意左上角的等价 SQL——你早就在写关系代数了,只是没这么叫它。

试着先选 σ genre='scifi',你会看到 6 行里留下 3 行(Left Hand、Dispossessed、Three-Body);再把投影换成 π title, price,列从 7 列砍到 2 列。行的多少由选择决定,列的多少由投影决定,两者正交、互不干扰——这正是关系代数把「行操作」和「列操作」分得清清楚楚的好处。

为什么这层抽象值得你在意

你可能会想:我会写 SELECT 就行了,管它叫不叫关系代数?但这层抽象,恰恰是理解本书后半程的钥匙:

  • 它解释了「声明式」(卷 V):你写的 SELECT … WHERE … 是一个关系代数表达式——它描述「要什么」,不描述「怎么算」。σ 和 π 谁先谁后、要不要用索引,是查询规划器的自由。同一个表达式,规划器可以用完全不同的物理方式去执行。这是 PostgreSQL 那个「会思考的大脑」有发挥空间的前提。
  • 它解释了集合语义(卷 II):DISTINCT 去重、UNION 默认去重、NULL 的三值逻辑——这些看似别扭的规则,都是「关系是集合」这条公理推出来的。理解了源头,就不用死记。
  • 它解释了 SQL 的可组合性:CTE(WITH)、子查询、视图之所以能像积木一样拼,全靠「关系运算的输出还是关系」这条闭包性质。
✎ 掌故 · 一篇论文,半个数据库工业

Codd 1970 年那篇《A Relational Model of Data for Large Shared Data Banks》,是计算机史上被引用最多的论文之一。在它之前,数据库是层次型、网状型的——数据靠指针串成一张网,查询要程序员手动「导航」这张网。Codd 说:别导航了,把数据看成规整的关系,用数学去查。SQL、以及后来所有关系数据库(包括 PostgreSQL 和 MySQL),都是这个想法的产物。PostgreSQL 的「当真」,某种程度上正是「把 Codd 的关系模型认真实现到底」。

⚠ 一个务实的提醒:SQL 不是纯粹的集合

说了半天「关系是集合」,得补一句实话:真实的 SQL 表其实是「多重集」(bag / multiset),允许重复行——只要你不加主键、不加唯一约束,两条一模一样的行是能同时存在的。这是 SQL 对纯关系模型的一处务实妥协(去重要排序,成本高,不该强制)。所以更准确的说法是:关系模型是理想,SQL 是它的工程化近似。理想解释了 为什么DISTINCT 这个词,工程化近似解释了为什么它不是默认。下一章建表时,我们就会用主键把「这张表的行不许重复」这条规矩当真地写下来。

⇄ 对照 MySQL

关系模型是 SQL 标准的共同地基,MySQL 和 PostgreSQL 在这一层是一致的——两家都把表当关系、都支持 σ / π / JOIN / 集合运算。真正的分歧在执行的严格程度

  • PostgreSQL 长期支持完整的集合运算 UNION / INTERSECT / EXCEPT;MySQL 直到 8.0.31 才补上 INTERSECTEXCEPT(早年只能用 JOIN 或子查询绕)。
  • 「行无序」这条,两家都成立——但都有各自的实现「巧合」会让你误以为有序。不写 ORDER BY 就别假设顺序,这条纪律在哪家都一样重要。

这一章的一句话

一张表是一个元组的集合,一句 SQL 是叠在集合上的关系运算(σ 筛行、π 选列……),运算的输出还是一张表——所以查询能无限嵌套。

抽象讲够了。下一章,我们把这套理论落地:建起一个会「当真」的示例库,它会陪你走完整本书。