Appearance
第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 常更直接,但仍需核对关联条件。
函数依赖与异常
函数依赖 表示任意两个元组只要 X 相同,Y 也相同。它是关于所有合法数据库实例的约束,不能仅从一份样本没有重复就断言成立。
若表 Enroll(sid,cid,teacher,score) 中每门课只有一位教师,则有 。教师信息随每个选课学生重复,改教师需修改多行,漏改会矛盾。
分解与无损连接
将其分为 Course(cid,teacher) 和 Enroll(sid,cid,score),在该依赖下可通过 cid 无损连接恢复原表。二元分解为 时,一个常用无损判据是交集属性在依赖闭包下决定其中一方全部属性。
BCNF 要求每个非平凡依赖的决定因素都是超键;3NF 放宽部分条件以有利于保持依赖。无损连接与依赖保持是不同目标,分解满足一个不自动满足另一个。
练习
- 用含 NULL 的三行选课数据计算
COUNT(*)、COUNT(score)和AVG(score)。 - 证明上述分解为何无损,并列出需要的函数依赖。
- 构造把一张表随意拆分后再连接产生额外元组的例子。