2.1 关系代数:用 relation 组合查询
SQL 让人写“要什么”,关系代数则提供一组可以组合、改写和推理的 operators。查询优化器不会把 SQL 文本逐字执行;它先形成某种 logical plan,再在保持语义的前提下寻找物理执行方案。
本课目标
- 定义 relation、tuple、attribute 与 schema;
- 使用 selection、projection、rename、union、difference 和 product;
- 从 product + selection 推导 join;
- 解释 set semantics 与 SQL bag/NULL semantics 的差异;
- 把简单 SQL 表达成关系代数树。
Relation 的最小模型
给定 schema:
Items(item_id, type_id, name, value_cents)
Types(type_id, code)经典关系模型中:
- relation 是一组具有相同 attributes 的 tuples;
- tuple 没有内在顺序;
- relation 也没有内在行顺序;
- 作为 set,相同 tuple 不重复;
- attribute 的值来自相应 domain。
SQL table 与这个数学模型并不完全相同。SQL 查询默认使用 bag/multiset semantics,结果可以重复;NULL 引入三值逻辑;某些类型和表达式还有实现定义行为。因此从关系代数推理 SQL 时,要明确采用 classical set algebra,还是含 duplicate/NULL 的扩展语义。
本课先学习经典算子,再标出 SQL 差异。SQL 示例沿用 1.1、1.2 已创建的 SQLite 表;代数中的 Items.type_id 对应实际列 catalog_items.item_type_id,Types.type_id 对应 item_types.item_type_id。
Selection σ
selection 按 predicate 选择 tuples,不改变 schema:
σ value_cents >= 50000 (Items)对应 SQL:
SELECT item_id, item_type_id AS type_id, name, value_cents
FROM catalog_items
WHERE value_cents >= 50000;输入 n 行,selection 输出在 0 到 n 行之间。输出顺序不受保证。
多个 conjunction 可连续选择:
σ type_id = 1 AND value_cents >= 50000 (Items)
= σ type_id = 1 (σ value_cents >= 50000 (Items))在 classical two-valued predicate 下这项等价直接成立。SQL 中 predicate 为 UNKNOWN 的行和 FALSE 一样被 WHERE 丢弃,但涉及 NULL、error-producing expression 或 volatile function 时,实际改写需由 optimizer 遵守目标数据库语义。
Projection π
projection 选择 attributes:
π type_id (Items)classical relational algebra 的结果是 set,因此重复的 type_id 会消除。对应 SQL 需要显式 DISTINCT:
SELECT DISTINCT item_type_id
FROM catalog_items;普通 SQL:
SELECT item_type_id
FROM catalog_items;保留 duplicate,不能直接当作 classical π。
projection 还可以在扩展代数中计算 expression,例如把 cents 换算成 major unit;但计算、类型转换和 rounding 要在语义中明确。
Rename ρ
同一 relation 与自己 join 时,必须区分两个实例:
ρ I1 (Items)
ρ I2 (Items)SQL alias 承担类似角色:
SELECT left_item.name, right_item.name
FROM catalog_items AS left_item
JOIN catalog_items AS right_item
ON left_item.item_type_id = right_item.item_type_id
AND left_item.item_id < right_item.item_id;最后一个 inequality 防止一对 item 以相反顺序出现两次,也排除自己与自己配对。
Union ∪
union 需要 union-compatible inputs:对应 attributes 的数量和 domain/类型兼容。
π item_id (ActiveTaskItems)
∪
π item_id (CompletedTaskItems)SQL UNION 消除 duplicate,更接近 set union;UNION ALL 保留 duplicate,通常也避免 duplicate elimination 的代价。
SELECT qi.item_id
FROM task_items AS qi JOIN tasks AS q ON q.task_id = qi.task_id
WHERE q.status = 'active'
UNION
SELECT qi.item_id
FROM task_items AS qi JOIN tasks AS q ON q.task_id = qi.task_id
WHERE q.status = 'completed';结果 column name 通常由第一支决定,类型兼容与 coercion 由具体数据库规则决定。
Set difference −
AllItemIds − ReferencedItemIdsSQL 标准 operator 是 EXCEPT,不同产品支持情况和 duplicate 规则有差异:
SELECT item_id FROM catalog_items
EXCEPT
SELECT item_id FROM task_items;也可用 NOT EXISTS 表达 anti-join,常能更明确处理 correlation 与 NULL:
SELECT v.item_id
FROM catalog_items AS v
WHERE NOT EXISTS (
SELECT 1
FROM task_items AS qi
WHERE qi.item_id = v.item_id
);不要机械用 NOT IN (subquery) 替代;subquery 产生 NULL 时,三值逻辑可能让结果全部变为非 TRUE。
Cartesian product ×
R × S 把 R 的每个 tuple 与 S 的每个 tuple 配对。若基数分别为 m 与 n,输出恰好为 m × n;只有后续筛选才会减少数量。
SELECT *
FROM catalog_items
CROSS JOIN item_types;product 本身很少是业务最终目标,但它是 theta join 的构造基础。遗漏 join predicate 导致意外 product,会迅速放大 rows 和成本。
Join 是 product + selection
theta join:
Items ⋈ Items.type_id = Types.type_id Types
= σ Items.type_id = Types.type_id (Items × Types)SQL:
SELECT
i.item_id,
i.name,
t.code
FROM catalog_items AS i
JOIN item_types AS t
ON t.item_type_id = i.item_type_id;logical equivalence 不表示引擎必须真的 materialize product。物理 plan 可以使用 nested-loop、hash join、merge join 或 index lookup,只要保持结果语义。
Natural join 的风险
natural join 根据同名 columns 自动形成 equality predicates。schema 将来增加同名 column 时,查询语义可能悄悄改变,因此生产 SQL 通常显式写 JOIN ... ON ... 或 USING (...)。
Relational expression tree
查询:找出 device 中价值至少 50000 cents 的名称。
SELECT i.name
FROM catalog_items AS i
JOIN item_types AS t
ON t.item_type_id = i.item_type_id
WHERE t.code = 'device'
AND i.value_cents >= 50000;一种 logical expression:
π i.name
(
σ t.code = 'device' AND i.value_cents >= 50000
(
ρ i(Items)
⋈ i.type_id = t.type_id
ρ t(Types)
)
)树形理解:
projection: i.name
└─ selection: t.code='device' AND i.value_cents>=50000
└─ inner join: i.type_id=t.type_id
├─ Items as i
└─ Types as t这棵树说明逻辑关系,没有指定 hash table、index 或 scan 顺序。
Derived operators
Intersection
R ∩ S = R − (R − S)SQL 可使用 INTERSECT,同样要检查产品支持与 duplicate semantics。
Semijoin
left semijoin 只返回左侧匹配 tuples,不附加右侧 attributes:
SELECT i.item_id, i.name
FROM catalog_items AS i
WHERE EXISTS (
SELECT 1
FROM task_items AS qi
WHERE qi.item_id = i.item_id
);用普通 inner join 再 SELECT 左表 columns 可能产生 duplicate;EXISTS 更直接表达“是否存在”。
Division
division 适合“满足所有要求”的查询:找出完成了 required set 中每一种 task 的对象。SQL 通常用 double NOT EXISTS、grouping + count 或 set difference 表达,必须处理 required set 为空时的 vacuous truth。
自检
- 为什么
SELECT item_type_id不是 classical projection? R × S在两边各 1000 tuples 时有多少 tuples?- 为什么 inner join 的 logical definition 不要求数据库生成完整 product?
NOT EXISTS与NOT IN在 NULL 存在时可能有什么差别?- 写出“所有被 active task 引用的 item”的 semijoin SQL。
本课小结
关系代数用少量可组合 operators 表达复杂查询。SQL 的 bag、NULL 和类型规则让它不完全等同于 classical algebra;优化和推理时必须先确定语义。下一课讨论哪些改写安全、哪些会因 outer join、duplicate 或 NULL 改变答案。