C1 绪论
数据库的基本概念
-
⭐数据库、DBMS、数据库系统
概念 含义 数据库 DB 长期存储、有组织、可共享的大量数据的集合 数据库管理系统 DBMS 管理数据库的软件,位于应用程序和数据库之间(如 MySQL、Oracle、SQLSever) **数据库系统 DBS ** 数据库、DBMS、应用程序、数据库管理员、硬件等构成的整体 数据库应用系统 面向具体业务开发的应用程序及其数据库环境 = 数据库 + DBMS + 应用程序 + 数据库管理员 +硬件环境 -
⭐文件系统 vs 数据库
对比角度 文件方式 数据库方式 分散/统一 数据分散在不同文件 数据统一组织和管理 共享 共享困难 支持多用户共享 冗余 冗余较多 可以控制冗余 耦合/独立 程序与数据耦合较强 具有数据独立性 性质 一致性、安全性难保证 DBMS 统一保证完整性、安全和并发 适用场景 适合简单、独立的小规模数据 适合长期、共享、结构化业务数据
数据模型的组成要素 & 常用数据模型
数据库的三级模式
-
从数据库应用开发人员角度看,数据库通常采用 三级模式结构
层次 描述内容 外模式 某一类用户看到的局部数据,也叫用户视图 模式/概念模式 整个数据库的全局逻辑结构 内模式 数据的物理存储结构、文件组织和存取路径 
两种数据独立性
- 逻辑数据独立性:概念模式改变时,外模式和应用程序尽量不用改变。
例如增加一个属性,只需调整外模式与概念模式之间的映像,原有应用仍可使用原来的视图。 - 物理数据独立性:内模式改变时,概念模式和应用程序不用改变。
例如把顺序文件改成 B+ 树索引,应用程序仍然执行相同的 SQL。
⭐关系的完整性约束
- 实体完整性
主码属性不能为空 NULL - 参照完整性
- 外码值要么为空,要么等于被参照关系的主码值
- 外码:
参照关系:R(拿着外码的表,比如学生表)
被参照关系:S(被引用主键的表,比如专业表)
- 用户定义的完整性
⭐码与相关概念
-
超码
能 唯一标识 元组的任意属性集。
- 关系:候选码是最小超码,候选码的任意超集都是超码。
- 例如:S(Sno, Sdept, Sage),若:Sno → Sno, Sdept, Sage,
{Sno}是候选码 也是超码,(Sno, Sdept)也是超码。
-
候选码(码)
能够唯一标识元组的 最小 超码
{Sno}是候选码 -
全码
如果关系的所有属性U才是R的码,称为全码
例:(演奏者、作品、听众),全码为 。P, W, A 都是关系R的主属性,在关系R中没有非主属性 -
主码
如果一个关系模式有多个候选码,则从中选一个作为主码。
- 例如:Student(Sno, Sname, Ssex, Sage, Sdept):Sno 是候选码,Sname 也是候选码。
→ 可以选 Sno 作为主码。
- 例如:Student(Sno, Sname, Ssex, Sage, Sdept):Sno 是候选码,Sname 也是候选码。
-
外码
R中 不是当前关系R的码,是另一关系的码,则 为 的外码。
例: 中 Sno不是码,Sno是 S(Sno,Sdept,Sage)的码,
则 是 的外码
FOREIGN KEY (deptno) REFERENCES Department(deptno)
外码的作用是:建立表之间的引用联系,并保证参照完整性。
例
- S(Sno, Sdept, Sage)中,只有一个候选码Sno,所以:Sno是关系S的主属性,Sdept和Sage是关系S的2个非主属性;
- SC(Sno, Cno, Grade)中,只有一个候选码(Sno, Cno),所以:Sno和Cno是关系SC的2个主属性,Grade是关系SC的非主属性。
- Student(Sno, Sname, Ssex, Sage, Sdept)中。有两个候选码:Sno 和 Sname(不允许学生同名),所以:Sno 和 Sname是关系Student的2个主属性,Ssex, Sage, Sdept是关系Student的3个非主属性。
⭐C2 关系代数
-
关系运算
选择:、投影: -
例
- 所有:除运算÷;只:

- 所有:除运算÷;只:
-
最大/最高 在当前的所有顾客中, 查询折扣(discnt)最高的顾客的编号?
令 , “折扣不是最高的顾客的id”
答案是
-
象集
该属性值对应的其他属性值集合,相当于除法
-
除运算 R ÷ S
找出R、S中相同的属性列(B、C),R 剩下的属性列(A) 作为结果。
的所有(,),包含S中的全部b、c。
- 例
⭐C3 SQL
- SQL基本执行结构
逻辑顺序:SELECT ...FROM ...WHERE ...GROUP BY ...HAVING ...ORDER BY ...;
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
对查询结果的处理
- 对查询结果排序 ORDER BY 属性名 ASC升序/DESC降序
查询选修了3号课程的学生的学号及其成绩,查询结果按分数降序排列。
SELECT Sno, GradeFROM SCWHERE Cno= '3'ORDER BY Grade DESC;
- 对查询结果分组 GROUP BY/HAVING
求各个课程号及相应的选课人数。
查询选修了3门以上课程的学生学号。SELECT Cno,COUNT(Sno)FROM SCGROUP BY Cno; // 按照Cno为一行,相同Cno为一行SELECT SnoFROM SCGROUP BY SnoHAVING COUNT(*) >3;
- 辨析 HAVING 与 WHERE:只有HAVING可以用AVG()等

- 辨析 HAVING 与 WHERE:只有HAVING可以用AVG()等
基本表 TABLE
-
定义基本表
PRIMARY KEY 主键,唯一标识一行数据,不能重复、不能为空
UNIQUE 唯一,不能重复,但能为空
外键FOREIGN KEY (cno) PREFERENCE course(cno) ON UPDATE/DELETE 规约条件CREATE TABLE Student(Sno CHAR(9) PRIMARY KEY, /* 列级完整性约束条件, Sno是**主码**/Sname CHAR(20) UNIQUE, /* **约束**,Sname的取值具有唯一性*/Ssex CHAR(2) NOT NULL CHECK (Ssex IN ('男', '女')),/* 非空且只能是男/女 */Sage SMALLINT,Sdept CHAR(20));CREATE TABLE enrollment(sno char(9) NOT NULL,clsno char(8) NOT NULL,grade INT CHECK (grade BETWEEN 0 AND 100),PRIMARY KEY(sno,clsno), -- 课程班代码和学号联合构成PRIMARY KEY);CREATE TABLE courseclass(clsno char(8) PRIMARY KEY,cno char(8) NOT NULL,tno char(9) ,-- 先定义再外键 + 违约规则FOREIGN KEY (cno) REFERENCES coures(cno) ON UPDATE CASCADE ON DELETE RESTRICT,FOREIGN KEY (tno) REFERENCES teachers(tno) ON UPDATE CASCADE ON DELETE SET NULL-- 父表更新cno,子表cno自动同步更新;父表删除cno,子表还有该课程就禁止删除-- 父表更新tno,子表tno自动同步更新;父表删除tno,子表对应的tno自动设为NULL);- 违约规则
什么是违约? 父表更新主键/删除父表记录会破坏参照关系,触发违约关键字 中文 含义 CASCADE 级联 父表改 / 删 → 子表自动跟着改 / 删 SET NULL 置空 父表删 → 子表外键自动设为 NULL RESTRICT 限制 / 拒绝 数据库直接禁止父表的改/删操作
- 违约规则
-
修改基本表(ALTER TABLE)
- ADD新增列/完整性约束
ALTER TABLE StudentADD COLUMN S_email VARCHAR(50) NOT NULL UNIQUE; -- 增加邮箱列,要求非空且唯一ADD CONSTRAINT chk_sex CHECK (Ssex IN ('男', '女')); -- 为 Ssex 增加 CHECK 约束(性别只能是男 / 女)
- DROP 删除列
ALTER TABLE StudentDROP COLUMN S_email CASCADE; --删除“邮箱”列,默认RESTRICTDROP CONSTRAINT chk_sex; -- 删除性别CHECK约束
- 修改列的定义
ALTER TABLE StudentALTER COLUMN Sage INT; -- 修改数据类型:年龄从SMALLINT改为INTALTER COLUMN Sname CHAR(30); -- 修改列长度(姓名从 CHAR (20) 改为 CHAR (30))ALTER TABLE Course ADD UNIQUE(Cname); -- 增加课程名称必须取唯一值的约束条件
- ADD新增列/完整性约束
-
删除基本表
DROP TABLE <表名>[RESTRICT| CASCADE];- RESTRICT:只有在该表没有被其他数据库对象依赖时,才能删除。
- CASCADE:删除该表的同时,还会删除依赖于该表的相关对象或依赖关系
视图 VIEW
- 视图的作用?
简化复杂查询;
为不同用户提供不同数据视图;
隐藏敏感属性,提高安全性;
阻止不符合条件的数据加入/修改成不符合条件的数据。 - 定义视图 AS
CREATE VIEW S_G(Sno,Gavg) -- 可选:列名ASSELECT Sno,AVG(Grade)FROM SCGROUP BY Sno;
- 查询视图
- 更新视图
- 删除视图
DROP VIEW S_G [RESTRICT | CASCADE]
- 字符串匹配
- ⭐字符匹配 LIKE/=
SELECT* FROM Student WHERE Sno LIKE '24%' -- 通配符,查询学号以24开头的学生的详细信息SELECT* FROM Student WHERE Sno = '24%' -- 查询学号就是'24%'学生的详细信息- ⭐字符串中的通配符
SELECT Sname, Sno, SsexFROM StudentWHERE Sname LIKE '刘%'; -- 查询所有姓刘学生的姓名、学号和性别SELECT SnameFROM StudentWHERE Sname LIKE '欧阳_'; -- 查询姓"欧阳"且全名为三个汉字的学生的姓名SELECT Sname, SnoFROM StudentWHERE Sname LIKE '_阳%'; -- 查询名字中第2个字为"阳"字的学生的姓名和学号SELECT Sname, Sno, SsexFROM StudentWHERE Sname NOT LIKE '刘%'; -- 查询所有不姓刘的学生姓名、学号和性别 - 多表连接 JOIN
使用 DISTINCT 是为了消除同一学生由于多条匹配记录产生的重复SELECT DISTINCT s.sid, s.snameFROM Student sJOIN Enroll e ON s.sid = e.sidJOIN Course c ON e.cid = c.cidWHERE c.cname = '数据管理基础'AND c.dept = '智软学院';
- DISTINCT 的位置:SELECT 子句;聚集函数内部。
SELECT DISTINCT deptFROM Student;COUNT(DISTINCT sid)AVG(DISTINCT score)SUM(DISTINCT amount)
- DISTINCT 的位置:SELECT 子句;聚集函数内部。
- 分组与 HAVING先Group 分组后 HAVING
- EXISTS
子查询有结果,返回TRUE;子查询无结果,返回FALSE
SELECT SnameFROM Student SWHERE EXISTS (SELECT *FROM SCWHERE SC.Sno = S.SnoAND Cno = '1' -- 从 Student 表里,找出那些‘存在’选修 1 号课程记录的学生); -- 查询选修了 1 号课程的学生SELECT SnameFROM Student SWHERE NOT EXISTS (SELECT *FROM SCWHERE SC.Sno = S.SnoAND Cno = '1'); -- 查询没选1号课程的学生
- 查询选修全部课程的学生
SELECT SnameFROM StudentWHERE NOT EXISTS ( -- ③ 除去没有选择课程的学生SELECT *FROM Course -- ② 有NOT EXISTS学生选择的课程中的:学生没有选择的课程WHERE NOT EXISTS (SELECT *FROM SCWHERE SC.Sno = Student.SnoAND SC.Cno = Course.Cno -- ① 对SC中的每门课程,去Student、Course中寻找有无记录) -- 学生选择的课程);✅ 理解:不存在一门课是这个学生没选的
- 例
- 查询选修全部课程的学生
⭐例题
- 所有课程都及格:HAVING MIN(grade)>=60
- 修完本院所有必修课
- 每组最大值
- 只讲授本院课程
⭐C4 数据库编程
| 方式 | 怎么使用 | 主要场景 |
|---|---|---|
| 交互式 SQL | 用户直接输入 SQL | 临时查询、数据库管理 |
| 嵌入式 SQL | 主语言+SQL | SQL与C、C++结合 |
| 过程化 SQL | SQL+变量、判断、循环 | 存储过程、函数、触发器 |
嵌入式SQL
- 主变量:能在SQL中使用的主语言变量
- 必须在声明区声明
-- 声明区EXEC SQL BEGIN DECLARE SECTION;char givensno[10];char hsname[21];int hsage;EXEC SQL END DECLARE SECTION;-- 声明区EXEC SQL select Sno, Cno, Gradeinto :hsno, :hcno, :hgrade, :gradeid -- 指示变量from Studentwhere Sno = :givensno ; -- 查找给定学号的学生的信息 - 指示变量 gradeid:说明成绩是否为空
取值 含义 0 返回的是正常非空值 大于 0 字符串被截断 -1 数据库值为 NULL - 用指示变量写入NULL:把成绩置为空
gradeIndicator = -1;EXEC SQL UPDATE SCSET Grade = :hgrade :gradeIndicator -- 实际写入的是 grade = NULLWHERE Sno = :hsnoAND Cno = :hcno;
- 用指示变量写入NULL:把成绩置为空
- 通信区 SQLCA:记录每条SQL执行后的状态信息
SQLCODE = 0:执行成功;
非0:没有找到数据、警告或者错误。if (SQLCA.SQLCODE != 0) {break;} -- 前一条执行SQL语句,然后检查:若不为0 → 退出 -
⭐游标 Cursor
游标是系统为查询结果建立的 数据缓冲区 和 当前位置指针,用于存放SQL查询的执行结果。解决SQL批量返回数据、主语言单条处理之间的矛盾
- 声明游标
EXEC SQL DECLARE 游标名 CURSOR FOR 查询语句; - 打开游标
EXEC SQL OPEN 游标名; - 提取数据
EXEC SQL FETCH 游标名 INTO 主变量; - 关闭游标
EXEC SQL CLOSE 游标名;
- 声明游标
- 例
依次检查某个系的学生记录,交互式更新某些学生年龄。
EXEC SQL BEGIN DECLARE SECTION; /*主变量声明开始*/char Deptname[21];char Hsno[10];char Hsname[21];char Hssex[3];int HSage;int NEWAGE;EXEC SQL END DECLARE SECTION; /*主变量声明结束*/long SQLCODE;EXEC SQL INCLUDE SQLCA; /*定义SQL通信区*/#include <sqlca.h>int main(void) /*C语言主程序开始*/{int count = 0;char yn; /*变量yn代表yes或no*/printf("Please choose the department name(CS/MA/IS): ");scanf("%s",deptname); /*为主变量deptname赋值*/EXEC SQL CONNECT TO TEST@localhost:54321 USER "SYSTEM"/"MANAGER"; /*连接数据库TEST*/-- 我现在要登录连接到数据库,数据库地址是本机,端口是 54321,数据库名字叫 TEST,登录账号是 SYSTEM,密码是 MANAGEREXEC SQL DECLARE SX CURSOR FOR /*定义游标SX*/SELECT Sno,Sname,Ssex,Sage /*游标SX对应的查询语句*/FROM StudentWHERE SDept = :deptname;EXEC SQL OPEN SX;/*打开游标SX,执行游标对应的查询,并指向查询结果的第一行*/for (;;) -- 循环读取游标里的每一条数据,直到游标读完 / 出错时退出{/* 推进游标,将当前数据放入主变量 */EXEC SQL FETCH SX INTO :Hsno, :Hsname, :Hssex, :HSage;if (sqlca.sqlcode != 0) { -- 操作不成功时退出break;}/* 是第一行的话,打印表头和学生信息 */if (count++ == 0) {printf("\n%-10s %-20s %-10s %-10s\n", "Sno", "Sname", "Ssex", "Sage");}/* 打印当前学生信息 */printf("%-10s %-20s %-10s %-10d\n", Hsno, Hsname, Hssex, HSage);/* 询问用户是否要更新该学生的年龄 */printf("UPDATE AGE(y/n)? ");do {scanf("%c", &yn);-- getchar(); --吃掉多余的换行符} while (yn != 'N' && yn != 'n' && yn != 'Y' && yn != 'y');-- 强制让用户必须输入 Y/y/ N/n 才往下走,输错就一直重新输/* 如果选择更新操作 */if (yn == 'y' || yn == 'Y') {printf("INPUT NEW AGE: ");scanf("%d", &NEWAGE);getchar();/* 嵌入式SQL更新语句*/EXEC SQL UPDATE StudentSET Sage = :NEWAGE -- Sage更新为读入的NWEAGEWHERE CURRENT OF SX;} /* 对当前游标指向的学生年龄进行更新 */}/* 收尾 */EXEC SQL CLOSE SX; -- 关闭游标SX,不再和查询结果对应EXEC SQL COMMIT WORK; -- 提交更新EXEC SQL DISCONNECT TEST; -- 断开数据库连接} -- 只在 EXEC SQL FETCH SX INTO 语句之后检查了状态码 SQLCA.SQLCODE-- 在其他嵌入式SQL语句之后,都没有检查SQL语句的执行状态码!-- Solution:添加异常处理👇
异常处理
whenever是嵌入式SQL的全局异常捕获机制预编译器在处理过程中,会根据WHENEVER语句的定义,在每一条嵌入式SQL语句之后添加下面的异常状态检查命令:EXEC SQL WHENEVER 条件 动作;if ( condition ) { action }- 常见执行异常
SQLERROR、NOT FOUND、SQLWARNING - 处理方法 CONTINUE、GOTO label、STOP、DO function | BREAK | CONTINUE
- 例
exec sql whenever sqlerror goto report_error ; -- SQL执行错误exec sql whenever not found goto notfound ; -- 查询/FETCH没有得到记录,用于结束游标循环
动态SQL
过程化SQL
⭐触发器
-
**什么是触发器?**当某张表发生插入、删除或修改时,数据库自动执行一段事先写好的程序。
学生成绩提高超过10%时,自动记录日志;
插入教师信息时,如果教授工资低于4000,自动改成4000;
删除数据时,自动备份被删除的数据。 -
格式 只要修改就会触发,满足条件才执行后续操作
CREATE TRIGGER 触发器名BEFORE | AFTERINSERT | UPDATE | DELETE(OR 其他操作)ON 表名FOR EACH ROWBEGIN-- 触发后自动执行的语句END;……
- 例1:成绩提高10%时记录日志
CREATE TRIGGER SC_T -- 创建名为 SC_T 的触发器AFTER UPDATE OF Grade ON SC -- SC 中的 Grade 修改完成后,触发该触发器REFERENCINGOLD ROW AS OldTuple, -- 代表修改前的旧记录NEW ROW AS NewTuple -- 代表修改后的新记录FOR EACH ROW -- 每修改一行执行一次WHEN (NewTuple.Grade >= 1.1 * OldTuple.Grade) -- 新成绩比旧成绩至少提高10%,触发器才会执行下面的操作INSERT INTO SC_U(Sno, Cno, OldGrade, NewGrade)VALUES( -- 把下面信息插入日志表 SC_UOldTuple.Sno,OldTuple.Cno,OldTuple.Grade,NewTuple.Grade);
- 例2:教授工资不能低于4000
插入/修改教师信息时,如果该教师是教授,并且工资低于4000,就自动把工资改成4000。CREATE TRIGGER Insert_Or_Update_SalBEFORE INSERT OR UPDATE ON TeacherREFERENCING NEW ROW AS newTupleFOR EACH ROWBEGINIF newTuple.Job = '教授'AND newTuple.Sal < 4000 THENnewTuple.Sal := 4000;END IF;END;
函数
- IS:声明部分:接下来存放查询结果的 局部变量、常量、游标等
- 例:计算总输入
CREATE OR REPLACE FUNCTION totalsal -- 创建/替换一个名为totalsal的函数(v_empno IN emp.empno%TYPE -- 调用者输入)RETURN NUMBER -- 函数最终返回一个 NUMBER 类型的数值IStotalsal1 NUMBER;BEGINSELECT sal + commINTO totalsal1FROM empWHERE empno = v_empno;-- 把v_empno编号的员工的 sal工资+comm奖金,结果存入totalsal1RETURN totalsal1;END;
存储过程

- AS 声明部分:接下来存放查询结果的 局部变量、常量、游标等
CREATE OR REPLACE PROCEDURE TRANSFER (inAccount IN Account.AccountNum%TYPE, -- 转入账户:IN调用者传入outAccount IN Account.AccountNum%TYPE, -- 转出账户amount IN NUMBER -- 转账金额)AS -- 内部局部变量/* 保存转出账户的余额 */totalDepositOut Account.Total%TYPE;/* 保存查询到的转入账户号 */inAccountNum Account.AccountNum%TYPE;
- IN 调用者传入
CREATE OR REPLACE PROCEDURE TRANSFER (inAccount IN Account.AccountNum%TYPE, -- 转入账户:IN调用者传入outAccount IN Account.AccountNum%TYPE, -- 转出账户amount IN NUMBER -- 转账金额)
- INTO 查询并保存到局部变 → 异常处理
SELECT AccountNumINTO inAccountNumFROM AccountWHERE AccountNum = inAccountFOR UPDATE;EXCEPTION/* SELECT INTO没有查询到账户 */WHEN NO_DATA_FOUND THENROLLBACK;RAISE_APPLICATION_ERROR(-20004,'转入账户或转出账户不存在');/* 处理其他异常 */WHEN OTHERS THENROLLBACK;RAISE;
- 例:转账存储过程
/*====================================================1. 创建账户基本表====================================================*/CREATE TABLE Account (AccountNum VARCHAR2(11) PRIMARY KEY, -- 账户号Total NUMBER(15, 2) DEFAULT 0 NOT NULL, -- 账户余额CONSTRAINT CK_Account_TotalCHECK (Total >= 0)-- 创建名为 CK_Account_Total 的检查约束,要求:账户余额必须大于或等于0);/*====================================================2. 插入测试数据====================================================*//* 转入账户:初始余额20000元 */INSERT INTO Account(AccountNum, Total)VALUES ('01003813828', 20000);/* 转出账户:初始余额50000元 */INSERT INTO Account(AccountNum, Total)VALUES ('01003815868', 50000);COMMIT; -- 提交插入操作/*====================================================3. 查看转账前的账户余额====================================================*/SELECT AccountNum, TotalFROM AccountORDER BY AccountNum;/*====================================================4. 创建转账存储过程====================================================*/CREATE OR REPLACE PROCEDURE TRANSFER (inAccount IN Account.AccountNum%TYPE, -- 转入账户:IN调用者传入outAccount IN Account.AccountNum%TYPE, -- 转出账户amount IN NUMBER -- 转账金额)AS -- 内部局部变量/* 保存转出账户的余额 */totalDepositOut Account.Total%TYPE;/* 保存查询到的转入账户号 */inAccountNum Account.AccountNum%TYPE;BEGIN/*------------------------------------------1. 检查转账金额:小于0 不合法------------------------------------------*/IF amount <= 0 THENRAISE_APPLICATION_ERROR(-20001,'转账金额必须大于0');END IF;/* 转入账户和转出账户不能相同 */IF inAccount = outAccount THENRAISE_APPLICATION_ERROR(-20002,'转入账户和转出账户不能相同');END IF;/*------------------------------------------2. 查询并锁定转出账户------------------------------------------*/SELECT TotalINTO totalDepositOut -- 查询并保存到局部变量totalDepositOutFROM AccountWHERE AccountNum = outAccountFOR UPDATE; -- 加行级锁。防止转账过程中,其他事务同时修改该账户余额。-- 一直到COMMIT/ROLLBACK/*------------------------------------------3. 检查转出账户余额:小于转账金额 抛出ERROR------------------------------------------*/IF totalDepositOut < amount THENRAISE_APPLICATION_ERROR(-20003,'转出账户余额不足');END IF;/*------------------------------------------4. 检查并锁定转入账户------------------------------------------*/SELECT AccountNumINTO inAccountNumFROM AccountWHERE AccountNum = inAccountFOR UPDATE;/*------------------------------------------5. 转出账户扣款------------------------------------------*/UPDATE AccountSET Total = Total - amountWHERE AccountNum = outAccount;/*------------------------------------------6. 转入账户加款------------------------------------------*/UPDATE AccountSET Total = Total + amountWHERE AccountNum = inAccount;/*------------------------------------------7. 提交转账事务------------------------------------------*/COMMIT;EXCEPTION/* SELECT INTO没有查询到账户 */WHEN NO_DATA_FOUND THENROLLBACK;RAISE_APPLICATION_ERROR(-20004,'转入账户或转出账户不存在');/* 处理其他异常 */WHEN OTHERS THENROLLBACK;RAISE;END TRANSFER;
静态SQL vs 动态SQL
C5 关系数据规范化理论
⭐数据依赖:函数依赖
- 定义
X确定唯一Y,则称“X函数确定Y”或“Y函数依赖于X”,记作 。- :决定因素
- :依赖因素
1. 平凡/非平凡函数依赖
- 平凡函数依赖: 且 (Sno本来就在左边) 例:,
- 非平凡函数依赖: 且 例:
2. 完全/部分函数依赖
- 完全函数依赖:要决定Y,X的所有部分都要用上,如要确定一门课的成绩,需要同时知道学号+课程名
例: - 部分函数依赖:要决定Y,只需要X中的一部分属性就可以,如确定专业,只需知道Sno就行,Cno是不必要的
例:
3. 传递函数依赖
- 定义:若 ,,( ),则
- 例:,
数据依赖:多值依赖
范式 & 规范化
- 范式(NF):是对一个关系中允许存在的数据依赖的要求
规范化:通过投影分解将低范式转换为高范式。 - 范式等级(从低到高包含关系):
- 规范化:将低范式通过模式分解转为高范式,消除不良数据依赖
范式越高,对表中数据依赖的要求越严格。
| 范式 | 主要解决的问题 | 一句话理解 |
|---|---|---|
| 1NF | 属性不可再分 | 每个格子只能放一个值 |
| 2NF | 部分函数依赖 | 非主属性不能由主属性部分函数依赖推出,必须完全依赖 |
| 3NF | 传递函数依赖 | 不存在非主属性对候选码的传递函数依赖 |
| BCNF | 决定因素不是码 | 所有非平凡函数依赖中的决定因素(左边),都必须是包含候选码 (是超码) |
| 4NF | - | 每个非平凡多值依赖 X →→ Y 都满足 X含有码 |
1NF:属性原子性;2NF:不存在主属性部分函数依赖非主属性;
3NF:不存在 非主属性 对 候选码的传递依赖;BCNF:左边都是 超码
部分主 → 非主;非主 候选码;超码 →
二、1NF(第一范式)
三、2NF(第二范式)
- 定义
非主属性不能由候选码部分函数依赖推出,必须完全依赖。- 如:AF 是候选码,D 是非主属性。
若A → D,说明非主属性 D对候选码部份依赖 不满足2NF
- 如:AF 是候选码,D 是非主属性。
- 实例:SLC 关系
- Solution
分解成2个关系模式(但插入异常、删除异常任然存在)
四、3NF(第三范式)
-
定义
不存在非主属性对候选码的传递函数依赖
码不能和 非主属性 有传递依赖关系
不存在码X、属性Y、非主属性Z使得 成立 -
实例:SL 关系(Sno是码)

五、BCNF(巴斯范式)
- 定义
所有非平凡函数依赖中的决定因素(左边),都必须是包含候选码(都必须是超码) - 实例 1:仓库管理关系:把违反的(管理员,仓库名)单独成表
4NF(第四范式)
- 定义
每个非平凡多值依赖 X →→ Y 都满足 X含有码 - 分解
把独立的多值事实拆开
⭐Armstrong公理系统
-
Armstrong公理系统 - 基本规则
- A1 自反律:知道一组属性,一定知道部分
{Cno, Sname} → Cno - A2 增广律:若 ,则
Cno → Sname {Cno, clsno} → {Sname, clsno} - A3 传递律:若 且 ,则
Cno → Class、Class → Teacher Cno → Teacher
- A1 自反律:知道一组属性,一定知道部分
-
Armstrong公理系统 -扩充规则
- 合并规则:若 X Y 且 X Z,则 X → YZ
- 分解规则:若:X → YZ,则:X → Y,X → Z
- 伪传递规则:若 X Y 且 WY Z,则 XW Z
- 引理: 成立
学号 → (姓名, 班级, 专业) 学号 → 姓名,学号 → 班级,学号 → 专业
-
函数依赖的逻辑蕴涵
- F 的所有规则,必然导致 X→Y 成立(X Y是属性/属性集)。是由F带来的,没有写出但必然成立的规则。
则称:F 逻辑蕴涵 X → Y,记作 F ⊨ X → Y - 例:F = {学号 → 系名, 系名 → 系主任 },则:F ⊨ 学号 → 系主任
- F 的所有规则,必然导致 X→Y 成立(X Y是属性/属性集)。是由F带来的,没有写出但必然成立的规则。
-
函数依赖集的覆盖 & 等价
- 核心概念
- 覆盖:若 F 能推出 G 里的所有依赖 () ,就说 F覆盖G
- 等价:若 F覆盖G 且 G覆盖F(),就说 F和G等价(充要关系)
- 例
设:F = { A → B, A → C },设:G = { A → BC }
F 和 G 等价。
因为:由 F 可用合并规则推出 A → BC;由 G 可用分解规则推出 A → B 和 A → C
所以:F⁺ = G⁺(“分开写”和“合起来写”本质上表达的是同一件事)
- 核心概念
-
函数依赖集
函数依赖:A→B 从A出发能确定唯一B。即一对一/多对一(不能出现多对一/多对多)
A→B 多对一/AC→D 一对一;但是A→B 多对一/A→C 多对多
-
3.1 函数依赖集的闭包 F^+
- 定义:,即:所有被 F 逻辑蕴涵的函数依赖的集合。
- 属性集 U={A,B,C},函数依赖集 F={A→B, B→C} → ={ A→A, A→B, A→C, A→BC, A→AB, A→AC, A→ABC, B→B, B→C, B→BC, C→C, AB→A, AB→B, AB→C, AB→AB, AB→AC, AB→BC, AB→ABC, AC→A, AC→B, AC→C, AC→AB, AC→AC, AC→BC, AC→ABC, BC→B, BC→C, BC→BC, ABC→A, ABC→B, ABC→C, ABC→AB, ABC→AC, ABC→BC, ABC→ABC }
- 直接计算 F⁺ 很麻烦。因为可能推出非常多的函数依赖。所以实际做题时,
一般不直接求 F⁺,而是用 属性集闭包 X⁺ 👇来判断某个依赖是否成立。
-
3.2 属性集的闭包
-
根据 F,属性集 A 最终能够确定哪些属性,称为属性A关于函数依赖集F的闭包。
-
⭐例:计算A关于F的闭包 (迭代算法)
初始: 用 A→B: 用 B→C:
- ⭐例:求候选码 若A能推导出所有属性 → A是候选码
-
-
3.3 极小函数依赖集 / 最小依赖集 / 最小覆盖:F的最简形式
-
需满足 3 个条件:
设 F 为函数依赖集, 是其极小依赖集,需满足:
- 右边只有一个属性
- 左边没有多余属性
AB → C,如果:A⁺ 中已经包含 C说明只用 A 就能推出 C,B 是多余的。
于是:AB → C可以简化为:A → C - 没有冗余函数依赖
-
计算算法
- 右部拆分:AD → EF 拆成:AD → E,AD → F;
- 删左边多余属性:对于 ADF→ C:∵AD → F ,故可简化为 AD → C;
对于 ADF → D:∵D已经包含在左边 可以删除该平凡函数依赖; - 删冗余依赖:对于C → B,可以由C → F,F → B推出。 删除 C → B;
- 特性:每个函数依赖集都等价于一个极小依赖集,且不唯一。
-
🌟例题
-
⭐例1:属性集闭包算法 -计算A关于F的闭包
初始: 用 A→B: 用 B→C:- 计算方法:迭代算法
- 计算方法:迭代算法
-
⭐例:等价/最小函数依赖集 -属性集闭包算法
-
⭐例2:极小函数依赖集算法
- 右部拆分:AD → EF 拆成:AD → E,AD → F;
- 删左边多余属性:对于 ADF→ C:∵AD → F ,故可简化为 AD → C;
对于 ADF → D:∵D已经包含在左边 可以删除该平凡函数依赖; - 删冗余依赖:对于C → B,可以由C → F,F → B推出。 删除 C → B;
-
⭐(补充3)例:判断冗余依赖
-
函数依赖集 M = {C → E,E → B,BCD → A},断言:BCD → A 可以简化成 CD → A,它是一个冗余函数依赖
构造 N = {C → E,E → B,CD → A},只需证明:(M能推出的=N能推出的)
✅只需证明:M ⊨ CD → A? N ⊨ BCD → A?① 证明:M ⊨ CD → A
只需证明 中包含 A,就能证明 CD → A
初始值:{CD}_M^+ = {C,D}
第一遍:{CD}_M^+ = {C,D,E}(C → E)
第二遍:{CD}_M^+ = {B,C,D,E}(E → B)
第三遍:{CD}_M^+ = {A,B,C,D,E}(BCD → A)② 同理得证:N ⊨ BCD → A
-
函数依赖集 M = {B → D,D → E,C → F,BC → A,EF → A},断言:BC→ A 是一个冗余的函数依赖
构造 N = {B → D,D → E,C → F,EF → A},要证明 ,
只需证明:N ⊨ BC → A,即: 包含A -
函数依赖集 F = {A → B,B → A,AB → C},判断 AB → C 是否为部分函数依赖?
只需假设把AB → C 换成 A → C/B → C是否依然成立。同上。
-
-
⭐例3:计算候选码
要求候选码K,只需找到能推出全部属性U的 即可。
- 没有出现在右边的属性 → 一定在候选码内(因为没有属性能推出它)
- 只出现在右边的属性 → 一般不放在候选码(因为可以由其他属性推出)
- 其他属性待选跟1结合
- 先算1必须出现在候选码中的属性K,是否有
- 再依次跟3中属性结合。候选码可能不止1个。
⭐模式分解
-
模式分解的三个理解
- 无损连接:自然连接后完全等于原表
- 保持函数依赖:分解后所有依赖规则都能找到
- 既 无损连接 又 保持函数依赖
-
⭐无损连接
-
有损连接:会产生多余元组。
-
定义:R = R1 ⋈ R2 ⋈ … ⋈ Rn,能 自然连接 回来,完全等于原表。
-
💫判定:两张表的公共属性,如果能推出某一张表的非公共属性,就是无损连接。
例如1:设有一个关系模式T(A,B,C),函数依赖集F, 请判断下述的 分解是否具有无损连接性?A是公共属性,能推出T1的非公共属性B。故满足无损连接性函数依赖集 分解ρ 是否满足无损连接性? 解释 {A→B} ✅ A是公共属性,能推出B {A→C,B→C} ✅ A是公共属性,能推出C {A→B} ❌ B是公共属性,不能推出非公共属性 {A→B,B→C} } ❌ C是公共属性,不能推出非公共属性
-
-
⭐保持函数依赖
分解后,原来的函数依赖能在子关系中直接检查。
🌟3NF模式分解
- 目标:得到 满足3NF 且 既有无损连接性又保持函数依赖 的分解
- 步骤:每条依赖建一张表,最后检查有没有候选码,没有就补上
- 求 F 的极小函数依赖集 Fmin
- 求候选码
- 谁决定谁,就把它们放在一张表里。
例如:Sno → Sdept就建:SD(Sno, Sdept) - 合并左边相同的
- 💫检查是否需要补候选码表:如果没有任何子关系包含原关系的候选码 K,则补一个关系 R(K)
- 💫例:到3NF的模式分解示例

🌟BCNF 模式分解
- 目标:分解到 BCNF,并且保证无损连接。
- 步骤:
- 初始 ρ = {R}
- 检查每个关系是否满足 BCNF
- 如果某个关系不满足 BCNF,则存在:X → Y,且 X 不是超码
- 分解为:R₁ = (X,Y),R₂ = (U - (Y-X))
例:U={A,B,C,D,E},X={A,B} Y={B,C}
→ Y-X={B,C}-{A,B}={C}减去公共属性
→ = U-(Y-X) = {A,B,D,E},={A,B,C} - 继续检查,直到全部满足 BCNF
-
💫例:转换为BCNF的无损连接分解
STJ(S学生,T老师,J课程),依赖包括:T → J,(S,J) → T,(S,T) → J,候选码为ST、SJ
STJ 满足 3NF,但不满足 BCNF。原因:T → J 中,T 不是超码。
所以分解为:TJ(T,J) ST(S,T)
多值依赖
- 定义:一个属性决定的不是一个值,而是一组值。记作 X →→ Y,读作Y多值依赖于X(有其他属性Z,Y、Z都多值依赖于X的充要条件是 X、Y可以自由组合)
- 多值依赖 vs 函数依赖
- 跟Z的关系
函数依赖:只问“X 能不能唯一确定 Y”。
多值依赖:还要问“Y 和剩下的属性是不是独立”。 - 跟子集的关系
A → BC,则:A → B A → C
多值依赖不一定成立
- 跟Z的关系
⭐C6 数据库的安全性
- 授权:授予GRANT 与 回收REVOKE
- 授予 GRANT
转授 WITH GRANT OPTIONGRANT SELECT,INSERTON StudentTO U1 -- 把对Student的查询、插入权限授予用户U1GRANT ALL PRIVILIGESON TABLE Student,CourseTO U2,U3 -- 把对Student表和Course表的全部权限授予用户U2和U3GRANT SELECTON StudentTO user2WITH GRANT OPTION;
- 回收 REVOKE
REVOKE UPDATE(Sno) ON STUDENT FROM U4 -- 把用户U4修改学生学号的权限收回REVOKE SELECT ON SC FROM PUBLIC -- 收回所有用户对表SC的查询权限
- 授予 GRANT
⭐C7 数据库设计
- 数据库设计基本步骤
| 工作 | 所属阶段 |
|---|---|
| 收集业务需求、数据流 | 需求分析 |
| 画 ER/EER 图 | 概念结构设计 |
| ER 转关系模式、规范化 | 逻辑结构设计 |
| 设计索引、文件组织、聚簇 | 物理结构设计 |
| 建表、装载数据、编程 | 数据库实施 |
⭐C8 ER/EER模型
-
父子关系-ISA联系:不相交 & 完全特化(超类&子类)
不相交约束 x:不能是多个
完全特化 ||:必须是一个

-
Part-of联系 双菱形

-
弱实体:双矩形。自己的属性不一定能独立区分自己(有不同贷款但可能在同一天还款、金额也可能一样)(不同职工可能都有叫“张伟”的家属;因此仅用“家属姓名”不能唯一确定家属)

-
基数约束表示:min(强制参与/非强制参与)..max(单值参与/多值参与)
左边基数:一个左边,对应多少个右边- 为什么引入:消除歧义
-
组合属性 / 多值属性
C9 数据库恢复技术
事务 & ACID特性
- 什么是事务?:原子操作,事务是一个不可分割的数据库操作序列:
- ACID四个特性
特性 含义 例子 主要由什么保证 Atomicity 原子性 要么全做,要么全不做 扣款和加款必须一起完成 UNDO、ROLLBACK、日志 Consistency 一致性 从一个一致状态变为另一个一致状态 转账前后总金额不变 正确事务程序、完整性约束、并发与恢复机制 Isolation 隔离性 并发事务之间互不干扰 别人不能看到只扣款、未加款的中间状态 第11章并发控制、封锁 Durability 持久性 提交后的结果不会因故障丢失 转账提交后,断电也不能丢失 REDO、日志、转储
COMMIT、ROLLBACK & 保存点 SAVEPOINT
3. SQVEPOINT
sql 操作1 操作2 SAVEPOINT sp1 -- 定义 操作3 操作4 ROLLBACK TO sp1 -- 操作3、操作4撤销;
三类故障及恢复方法
这是判断UNDO、REDO的基础。
| 故障类型 | 是否破坏磁盘数据库 | 典型原因 | 恢复方法 |
|---|---|---|---|
| 事务故障:程序错误 | 通常不破坏数据库文件 | 溢出、死锁、违反完整性约束 | UNDO该事务 |
| 系统故障:外界错误CPU/OS/DBMS/停电 | 不破坏磁盘文件,但内存丢失 | 断电、操作系统崩溃、DBMS崩溃 | UNDO未提交,REDO已提交 |
| 介质故障:磁盘损坏/强磁场造成外存数据丢失 | 破坏数据库文件 | 磁盘损坏、磁头碰撞 | 重装副本,再REDO |
| 病毒等人为故障 | 可能破坏数据或文件 | 恶意修改、删除 | 管理员:后备副本+日志REDO |
数据转储
- 静态转储 & 动态转储
- 海量存储 & 增量存储
⭐日志文件
- **什么是日志文件?**日志文件记录事务对数据库的更新操作。
- UNDO前像 & REDO后像
<START T1><T1, A, 100, 80>-- 账户 A 原来余额为100元,事务T1要扣除20元→修改为80元<COMMIT T1> -- 事务T1已经成功完成,修改结果应当永久保存<START T2><T2, B, 50, 70><ROLLBACK T2> -- T2放弃事务,撤销自己的全部修改。回滚回前像B=50<CHECKPOINT ...>
- T1没有提交,系统故障
没有COMMIT,T1尚未完成 → UNDO T1使用前像,把A恢复成100 - T1已经提交,系统故障
CONNIT已经提交,但80还没写入磁盘 → REDO T1使用后像,A写入80
- T1没有提交,系统故障