课程 › 第 12 章 › 实验一
第 12 章 · 实验一 · 在第二节之后

用数据检验函数依赖

在 SCD 表上数一数重复存放的数据,做一次更新异常,再用 GROUP BY 和 HAVING 给函数依赖找反例。

环 境

MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。

执 行

Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。

注 意

输入法切到英文。表名、字段名不加引号,字符串值才用单引号。

本次实验的顺序 后一个实验用到前一个的结果

  1. 实验 1-1数一数重复的数据
  2. 实验 1-2换一次系主任
  3. 实验 1-3学号决定姓名、年龄、系别
  4. 实验 1-4系别与系主任
  5. 实验 1-5学号与课程号
  6. 实验 1-6完全依赖与部分依赖
第一步

数据准备(必做)

新建数据库 norm 和课件第 5 页的 SCD 表,装入 10 行数据。C7 的成绩还没有给出,是空值。两次实验共用这份脚本,每次实验前执行一次。

整段复制,全部执行下载 .sql
-- 第 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;
1-1

数一数重复的数据

讲义 · SCD 泛关系讲义 · 四种问题
目的
查看 SCD 表,统计系别和系主任、学号和姓名各重复存放了多少次。
前提
已执行数据准备,当前库是 norm。
  1. 查询 SCD 的全部数据
  2. 按系别和系主任分组,统计行数
  3. 按学号和姓名分组,统计行数

参考答案

可以直接复制到 Workbench 执行
SELECT * FROM scd;
-- 每个系和它的系主任存了几次
SELECT Dept, MN, COUNT(*) AS 行数
FROM scd GROUP BY Dept, MN;
-- 每个学生的学号和姓名存了几次
SELECT SNo, SN, COUNT(*) AS 行数
FROM scd GROUP BY SNo, SN;

语句解释

GROUP BY Dept, MN系别和系主任都相同的行分成一组
COUNT(*)这一对信息在表中存了几次
数据冗余系主任本来只需要记一次,现在每个学生的每门课都要再记一次
执行后看:信息系和王平存了 7 次,计算机系和刘伟 2 次,自动化系和刘伟 1 次。钱尔的学号和姓名存了 4 次。
1-2

换一次系主任

讲义 · 四种问题
目的
信息系的系主任换成张强,只修改其中一行,观察更新异常,再把这一行改回去。
前提
已完成实验 1-1。
  1. 只把学号 S2、课程号 C5 那一行的系主任改为张强
  2. 再按系别和系主任分组统计
  3. 把这一行改回王平

参考答案

可以直接复制到 Workbench 执行
UPDATE scd SET MN = '张强'
WHERE SNo = 'S2' AND CNo = 'C5';
SELECT Dept, MN, COUNT(*) AS 行数
FROM scd GROUP BY Dept, MN;
-- 改回原来的值,后面的实验还要用
UPDATE scd SET MN = '王平'
WHERE SNo = 'S2' AND CNo = 'C5';

语句解释

WHERE SNo = 'S2' AND CNo = 'C5'用主码定位到一行
更新异常信息系的系主任存了 7 份,只改一份,同一个系就有了两个系主任
Dept → MN按语义一个系只有一名系主任,现在表中的数据违反了它
执行后看:统计结果中信息系出现两行:王平 6 行,张强 1 行。改回以后恢复为王平 7 行。
1-3

学号决定姓名、年龄、系别

讲义 · 函数依赖的定义
目的
检验 SNo → SN、SNo → Age、SNo → Dept 在表中有没有反例。
前提
已完成实验 1-2。
  1. 按学号分组,统计每个学号对应几种姓名、几种年龄、几个系
  2. 只列出有反例的学号

参考答案

可以直接复制到 Workbench 执行
-- 同一个学号对应的姓名多于一种,就是 SNo 决定 SN 的反例
SELECT SNo, COUNT(DISTINCT SN), COUNT(DISTINCT Age), COUNT(DISTINCT Dept)
FROM scd GROUP BY SNo;
-- 只列出有反例的学号
SELECT SNo FROM scd GROUP BY SNo
HAVING COUNT(DISTINCT SN) > 1
    OR COUNT(DISTINCT Age) > 1
    OR COUNT(DISTINCT Dept) > 1;

语句解释

COUNT(DISTINCT SN)这个学号对应了几种不同的姓名
HAVING … > 1筛选出对应多于一种的组,这些就是反例
结果为空找不到反例。但函数依赖由语义决定,数据只能否定它,不能证明它
执行后看:第一个查询中每个学号的三个计数都是 1。第二个查询没有结果,找不到反例。
1-4

系别与系主任

讲义 · 传递函数依赖
目的
检验 Dept → MN 和 MN → Dept,看哪一个方向成立。
前提
已完成实验 1-3。
  1. 找 Dept → MN 的反例
  2. 找 MN → Dept 的反例

参考答案

可以直接复制到 Workbench 执行
-- 一个系对应多名系主任,就是 Dept 决定 MN 的反例
SELECT Dept FROM scd GROUP BY Dept
HAVING COUNT(DISTINCT MN) > 1;
-- 一名系主任对应多个系,就是 MN 决定 Dept 的反例
SELECT MN, COUNT(DISTINCT Dept) AS 系的个数
FROM scd GROUP BY MN
HAVING COUNT(DISTINCT Dept) > 1;

语句解释

Dept → MN找不到反例,一个系只有一名系主任
MN → Dept刘伟同时是计算机系和自动化系的系主任,这就是反例,不成立
传递依赖SNo → Dept,Dept → MN,而 Dept 决定不了 SNo,所以 MN 传递函数依赖于 SNo
执行后看:第一个查询没有结果。第二个查询得到刘伟,系的个数为 2。
1-5

学号与课程号

讲义 · 函数依赖与联系
目的
检验 SNo → CNo 和 CNo → SNo,理解多对多的属性之间没有函数依赖。
前提
已完成实验 1-4。
  1. 找 SNo → CNo 的反例
  2. 找 CNo → SNo 的反例

参考答案

可以直接复制到 Workbench 执行
SELECT SNo, COUNT(DISTINCT CNo) AS 课程数
FROM scd GROUP BY SNo
HAVING COUNT(DISTINCT CNo) > 1;
SELECT CNo, COUNT(DISTINCT SNo) AS 学生数
FROM scd GROUP BY CNo
HAVING COUNT(DISTINCT SNo) > 1;

语句解释

SNo → CNo一个学生选多门课,不成立
CNo → SNo一门课有多个学生选,也不成立
多对多学号与课程号是多对多的联系,两个方向都没有函数依赖
执行后看:第一个查询得到 S1、S2、S3,第二个得到 C1、C2、C4,两个方向都有反例。
1-6

完全依赖与部分依赖

讲义 · 完全与部分依赖
目的
检验 (SNo, CNo) → Score 是完全函数依赖,(SNo, CNo) → SN 是部分函数依赖。
前提
已完成实验 1-5。
  1. 去掉 CNo 只看 SNo,检验 SNo → Score
  2. 去掉 SNo 只看 CNo,检验 CNo → Score
  3. 检验 SNo → SN

参考答案

可以直接复制到 Workbench 执行
-- 只用学号能不能决定成绩
SELECT SNo FROM scd GROUP BY SNo
HAVING COUNT(DISTINCT Score) > 1;
-- 只用课程号能不能决定成绩
SELECT CNo FROM scd GROUP BY CNo
HAVING COUNT(DISTINCT Score) > 1;
-- 只用学号能不能决定姓名
SELECT SNo FROM scd GROUP BY SNo
HAVING COUNT(DISTINCT SN) > 1;

语句解释

(SNo, CNo) → Score主码能决定成绩。去掉任何一个属性后都有反例,所以是完全函数依赖
(SNo, CNo) → SN主码能决定姓名,但只用 SNo 也能决定,所以是部分函数依赖
部分依赖下一节会讲,第二范式就是要消除非主属性对码的部分依赖
执行后看:前两个查询都有结果,只用学号或只用课程号都决定不了成绩。第三个查询没有结果,只用学号就能决定姓名。
保山学院·人工智能教研室·曹鼎鼎