Skip to content

第2章:关系代数、SQL 与模式设计

查询的组合

选择筛行,投影筛列,连接按条件组合元组。查询“成绩至少 90 分的学生姓名”可先筛选 Enroll,再与 Student 连接、投影姓名。

sql
SELECT DISTINCT s.sid, s.name
FROM Student AS s
JOIN Enroll AS e ON e.sid = s.sid
WHERE e.score >= 90;

保留 sid 是为了区分同名学生。若只选姓名再去重,两个同名学生会合成一行,语义就变为不同姓名集合。

分组与 NULL

WHERE 在分组前过滤行,HAVING 在分组后过滤组。COUNT(*) 统计行数,COUNT(score) 通常只统计 score 非 NULL 的行。

sql
SELECT cid, COUNT(*) AS n, AVG(score) AS mean_score
FROM Enroll
GROUP BY cid
HAVING COUNT(*) >= 10;

这表示至少十条选课记录的课程,平均分则忽略 NULL 分数。如果要求至少十个已出分学生,需要使用相应非 NULL 计数。

NOT IN 子查询含 NULL 时可能得到 UNKNOWN,不能随意替换为直觉中的集合差。表达不存在匹配行时,相关 NOT EXISTS 常更直接,但仍需核对关联条件。

函数依赖与异常

函数依赖 XYX\to Y 表示任意两个元组只要 X 相同,Y 也相同。它是关于所有合法数据库实例的约束,不能仅从一份样本没有重复就断言成立。

若表 Enroll(sid,cid,teacher,score) 中每门课只有一位教师,则有 cidteachercid\to teacher。教师信息随每个选课学生重复,改教师需修改多行,漏改会矛盾。

分解与无损连接

将其分为 Course(cid,teacher)Enroll(sid,cid,score),在该依赖下可通过 cid 无损连接恢复原表。二元分解为 R1,R2R_1,R_2 时,一个常用无损判据是交集属性在依赖闭包下决定其中一方全部属性。

BCNF 要求每个非平凡依赖的决定因素都是超键;3NF 放宽部分条件以有利于保持依赖。无损连接与依赖保持是不同目标,分解满足一个不自动满足另一个。

练习

  1. 用含 NULL 的三行选课数据计算 COUNT(*)COUNT(score)AVG(score)
  2. 证明上述分解为何无损,并列出需要的函数依赖。
  3. 构造把一张表随意拆分后再连接产生额外元组的例子。

上次更新: