按课件的两步分解,把 SCD 拆成学生 S、选课 SC、系 D 三张表,用自然连接连回来核对,再故意切错一次,最后看三种异常是否消失。
MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。
Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。
输入法切到英文。表名、字段名不加引号,字符串值才用单引号。
与实验一相同的脚本,执行后 norm 库中只有 SCD 一张表。
-- 第 12 章实验数据:课件第 5 页的泛关系 SCD,10 行数据 -- 整段执行一次即可,想从头再做时重新执行。两次实验共用。 DROP DATABASE IF EXISTS norm; CREATE DATABASE norm DEFAULT CHARACTER SET utf8mb4; USE norm; -- SNo 学号,SN 姓名,Age 年龄,Dept 系别,MN 系主任,CNo 课程号,Score 成绩 CREATE TABLE scd ( SNo CHAR(4) NOT NULL, SN VARCHAR(10), Age INT, Dept VARCHAR(10), MN VARCHAR(10), CNo CHAR(4) NOT NULL, Score INT, PRIMARY KEY (SNo, CNo) ); INSERT INTO scd VALUES ('S1', '赵亦', 17, '计算机', '刘伟', 'C1', 90), ('S1', '赵亦', 17, '计算机', '刘伟', 'C2', 85), ('S2', '钱尔', 18, '信息', '王平', 'C5', 57), ('S2', '钱尔', 18, '信息', '王平', 'C6', 80), ('S2', '钱尔', 18, '信息', '王平', 'C7', NULL), ('S2', '钱尔', 18, '信息', '王平', 'C4', 70), ('S3', '孙珊', 20, '信息', '王平', 'C1', 75), ('S3', '孙珊', 20, '信息', '王平', 'C2', 70), ('S3', '孙珊', 20, '信息', '王平', 'C4', 85), ('S4', '李思', 21, '自动化', '刘伟', 'C1', 93); SELECT * FROM scd;
CREATE TABLE sc ( SNo CHAR(4) NOT NULL, CNo CHAR(4) NOT NULL, Score INT, PRIMARY KEY (SNo, CNo) ); INSERT INTO sc SELECT SNo, CNo, Score FROM scd; CREATE TABLE sd ( SNo CHAR(4) NOT NULL, SN VARCHAR(10), Age INT, Dept VARCHAR(10), MN VARCHAR(10), PRIMARY KEY (SNo) ); -- 每个学生只取一行 INSERT INTO sd SELECT DISTINCT SNo, SN, Age, Dept, MN FROM scd; SELECT * FROM sd;
| sc | 只有 Score 一个非主属性,它完全依赖于 (SNo, CNo) |
| sd | 主码是单个属性 SNo,不可能有部分依赖,达到第二范式 |
| SELECT DISTINCT | 同一个学生在 SCD 中有几行,这里只留一行 |
CREATE TABLE s ( SNo CHAR(4) NOT NULL, SN VARCHAR(10), Age INT, Dept VARCHAR(10), PRIMARY KEY (SNo) ); INSERT INTO s SELECT SNo, SN, Age, Dept FROM sd; CREATE TABLE d ( Dept VARCHAR(10) NOT NULL, MN VARCHAR(10), PRIMARY KEY (Dept) ); INSERT INTO d SELECT DISTINCT Dept, MN FROM sd; SELECT * FROM d;
| 在 Dept 处切开 | Dept 留在 s 中,同时作为 d 的主码,两张表靠它联系起来 |
| d | 一个系只存一行,系主任只存一次 |
| 第三范式 | s、sc、d 中都没有非主属性对码的部分依赖和传递依赖 |
SELECT * FROM s NATURAL JOIN sc NATURAL JOIN d; SELECT COUNT(*) FROM s NATURAL JOIN sc NATURAL JOIN d;
| NATURAL JOIN | 自然连接,按同名的列相等来连接,同名的列只保留一个 |
| s 和 sc | 按 SNo 连接 |
| 再连 d | 按 Dept 连接 |
| 无损连接 | 连回来的数据与分解前完全相同,一行不多,一行不少 |
-- CREATE TABLE … AS SELECT 用查询结果直接建表 CREATE TABLE sd1 AS SELECT SNo, SN, Age, MN FROM sd; CREATE TABLE sd2 AS SELECT DISTINCT Dept, MN FROM sd; SELECT * FROM sd1 NATURAL JOIN sd2;
| CREATE TABLE … AS SELECT | 按查询结果的列建表,同时装入数据 |
| 按 MN 连接 | 刘伟同时是两个系的系主任,MN 决定不了 Dept |
| 有损分解 | 连回来多出了原来没有的行,信息被弄乱了 |
-- 没有学生的系也能记下来 INSERT INTO d VALUES ('数学', '赵明'); -- 换系主任只改一行 UPDATE d SET MN = '张强' WHERE Dept = '信息'; -- 删除选课记录,系的信息还在 DELETE FROM sc WHERE SNo = 'S4'; SELECT * FROM d;
| INSERT INTO d | 系有自己的表和主码,不需要学号和课程号 |
| UPDATE d | 系主任只存了一次,改一行就完成 |
| DELETE FROM sc | 删除的只是选课记录,学生和系的信息都还在 |