按转换规则把 1:n、1:1、m:n、三个实体之间、同一实体内部的联系逐一建成表,最后按案例做一次模式合并和水平分解。
MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。
Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。
输入法切到英文。表名、字段名不加引号,字符串值才用单引号。
新建一个空的 er13 库。实验一建的表会被删除,本次实验的表都在实验中建立。
-- 第 13 章实验二数据:新建一个空的 er13 库,表都在实验中建立 -- 整段执行一次即可,想从头再做时重新执行。 DROP DATABASE IF EXISTS er13; CREATE DATABASE er13 DEFAULT CHARACTER SET utf8mb4; USE er13;
CREATE TABLE dept ( dno CHAR(2) NOT NULL, dname VARCHAR(20), PRIMARY KEY (dno) ); -- 联系并入 n 端,加上 1 端的码作为外码 CREATE TABLE student ( sno CHAR(4) NOT NULL, sname VARCHAR(10), dno CHAR(2), PRIMARY KEY (sno), FOREIGN KEY (dno) REFERENCES dept (dno) ); INSERT INTO dept VALUES ('d1', '计算机系'), ('d2', '信息系'); INSERT INTO student VALUES ('s1', '王彤', 'd1'), ('s2', '苏乐', 'd2'), ('s3', '林欣怡', 'd2'); -- 系号 d9 不存在,报错 1452 INSERT INTO student VALUES ('s4', '陶然', 'd9');
| 1:n 联系 | 转换出的关系模式主码与 n 端相同,所以直接并入 n 端 |
| FOREIGN KEY (dno) | 学生所在的系必须是系表中存在的系 |
| 报错 1452 | 外码的值在被参照的表中找不到 |
CREATE TABLE director ( tno CHAR(4) NOT NULL, tname VARCHAR(10), PRIMARY KEY (tno) ); INSERT INTO director VALUES ('t1', '刘伟'), ('t2', '王平'); -- 并入一端,UNIQUE 保证一对一 ALTER TABLE dept ADD tno CHAR(4) UNIQUE, ADD FOREIGN KEY (tno) REFERENCES director (tno); UPDATE dept SET tno = 't1' WHERE dno = 'd1'; SELECT * FROM dept; -- t1 已经领导计算机系,报错 1062 UPDATE dept SET tno = 't1' WHERE dno = 'd2';
| 1:1 联系 | 可以并入任意一端,每个实体的码都是候选码 |
| UNIQUE | 没有这个约束,一名系主任就能领导几个系,变成了 1:n |
| 报错 1062 | tno 的值 t1 已经存在,违反唯一约束 |
CREATE TABLE course ( cno CHAR(4) NOT NULL, cname VARCHAR(20), PRIMARY KEY (cno) ); CREATE TABLE sc ( sno CHAR(4) NOT NULL, cno CHAR(4) NOT NULL, score INT, PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student (sno), FOREIGN KEY (cno) REFERENCES course (cno) ); INSERT INTO course VALUES ('c1', '数据库'), ('c2', '高等数学'); INSERT INTO sc VALUES ('s1', 'c1', 90), ('s1', 'c2', 85), ('s2', 'c1', 78); SELECT s.sname, c.cname, sc.score FROM sc JOIN student s ON sc.sno = s.sno JOIN course c ON sc.cno = c.cno;
| m:n 联系 | 必须单独成表,不能并入任何一端 |
| PRIMARY KEY (sno, cno) | 码是两个实体码的组合 |
| score | 成绩是选修这个联系的属性,放在 sc 中 |
CREATE TABLE supplier (spno CHAR(4) NOT NULL, spname VARCHAR(20), PRIMARY KEY (spno)); CREATE TABLE project (pjno CHAR(4) NOT NULL, pjname VARCHAR(20), PRIMARY KEY (pjno)); CREATE TABLE part (ptno CHAR(4) NOT NULL, ptname VARCHAR(20), PRIMARY KEY (ptno)); CREATE TABLE supply ( spno CHAR(4) NOT NULL, pjno CHAR(4) NOT NULL, ptno CHAR(4) NOT NULL, qty INT, PRIMARY KEY (spno, pjno, ptno), FOREIGN KEY (spno) REFERENCES supplier (spno), FOREIGN KEY (pjno) REFERENCES project (pjno), FOREIGN KEY (ptno) REFERENCES part (ptno) ); INSERT INTO supplier VALUES ('sp1', '华信公司'); INSERT INTO project VALUES ('pj1', '教学楼'), ('pj2', '图书馆'); INSERT INTO part VALUES ('pt1', '螺栓'); INSERT INTO supply VALUES ('sp1', 'pj1', 'pt1', 200), ('sp1', 'pj2', 'pt1', 500); SELECT * FROM supply;
| 三个实体的联系 | 转换为一个关系模式,码是三个实体码的组合 |
| qty | 供应数量是联系的属性 |
| 另设主码 | 码太长时,也可以另加一个自增的编号做主码 |
CREATE TABLE worker ( wno CHAR(4) NOT NULL, wname VARCHAR(10), leader CHAR(4), PRIMARY KEY (wno), FOREIGN KEY (leader) REFERENCES worker (wno) ); -- 赵明没有领导,leader 为空 INSERT INTO worker VALUES ('w1', '赵明', NULL), ('w2', '钱红', 'w1'), ('w3', '孙磊', 'w1'); SELECT w.wname AS 职工, l.wname AS 领导 FROM worker w LEFT JOIN worker l ON w.leader = l.wno;
| leader | 领导的工号,外码参照同一张表的主码 |
| 自身连接 | 把 worker 当作两张表 w 和 l,按 leader = wno 连接 |
| LEFT JOIN | 没有领导的职工也显示出来 |
CREATE TABLE address ( aid CHAR(10) NOT NULL, contact VARCHAR(10), phone CHAR(11), PRIMARY KEY (aid) ); -- 拥有联系单独转换出的模式,主码也是地址编号 CREATE TABLE owns ( aid CHAR(10) NOT NULL, uid INT, PRIMARY KEY (aid) ); INSERT INTO address VALUES ('A000000001', '李娜', '13800002876'), ('A000000002', '李娜', '13800002876'); INSERT INTO owns VALUES ('A000000001', 1), ('A000000002', 1); ALTER TABLE address ADD uid INT; UPDATE address a JOIN owns o ON a.aid = o.aid SET a.uid = o.uid WHERE a.aid <> ''; DROP TABLE owns; SELECT * FROM address;
| 合并的条件 | 两个模式的主码相同,一行对一行 |
| UPDATE … JOIN | 按地址编号把 owns 中的用户编号写到 address 中 |
| WHERE a.aid <> '' | Workbench 的安全更新模式要求 WHERE 中用到主码,这个条件对所有行都成立 |
CREATE TABLE contain_2020 ( ono CHAR(17) NOT NULL, gno CHAR(11) NOT NULL, qty INT, PRIMARY KEY (ono, gno) ); CREATE TABLE contain_2021 LIKE contain_2020; INSERT INTO contain_2020 VALUES ('20200410140000001', '11000000112', 5); INSERT INTO contain_2021 VALUES ('20210105090000001', '11000000112', 2), ('20210105090000001', '21200000109', 1); SELECT * FROM contain_2020 UNION ALL SELECT * FROM contain_2021;
| 水平分解 | 按行切开,每张表的结构相同,只是存放不同时期的数据 |
| CREATE TABLE … LIKE | 按已有的表建立结构相同的空表 |
| UNION ALL | 把几张表的行合在一起,不去掉重复行 |