10608 字
53 分钟
数据库复习
2026-06-27

C1 绪论#

数据库的基本概念#

  1. ⭐数据库、DBMS、数据库系统

    概念含义
    数据库 DB长期存储、有组织、可共享的大量数据的集合
    数据库管理系统 DBMS管理数据库的软件,位于应用程序和数据库之间(如 MySQL、Oracle、SQLSever)
    **数据库系统 DBS **数据库、DBMS、应用程序、数据库管理员、硬件等构成的整体
    数据库应用系统面向具体业务开发的应用程序及其数据库环境 = 数据库 + DBMS + 应用程序 + 数据库管理员 +硬件环境
  2. ⭐文件系统 vs 数据库

    对比角度文件方式数据库方式
    分散/统一数据分散在不同文件数据统一组织和管理
    共享共享困难支持多用户共享
    冗余冗余较多可以控制冗余
    耦合/独立程序与数据耦合较强具有数据独立性
    性质一致性、安全性难保证DBMS 统一保证完整性、安全和并发
    适用场景适合简单、独立的小规模数据适合长期、共享、结构化业务数据

数据模型的组成要素 & 常用数据模型#

  1. ⭐数据模型#

    • 两大类、共三种数据模型:概念数据模型、 逻辑数据模型、物理数据模型
    • 数据模型的组成要素:数据结构、数据操作、数据约束 (二维关系表、原则/插入等、实体完整性)
    • ⭐关系模型基本概念#

      象集1
      象集2

数据库的三级模式#

  1. 从数据库应用开发人员角度看,数据库通常采用 三级模式结构

    层次描述内容
    外模式某一类用户看到的局部数据,也叫用户视图
    模式/概念模式整个数据库的全局逻辑结构
    内模式数据的物理存储结构、文件组织和存取路径

    图片描述

两种数据独立性#

  1. 逻辑数据独立性:概念模式改变时,外模式和应用程序尽量不用改变。
    例如增加一个属性,只需调整外模式与概念模式之间的映像,原有应用仍可使用原来的视图。
  2. 物理数据独立性:内模式改变时,概念模式和应用程序不用改变。
    例如把顺序文件改成 B+ 树索引,应用程序仍然执行相同的 SQL。

⭐关系的完整性约束#

  1. 实体完整性
    主码属性不能为空 NULL
  2. 参照完整性
    1. 外码值要么为空,要么等于被参照关系的主码值
    2. 外码:
      参照关系:R(拿着外码的表,比如学生表)
      被参照关系:S(被引用主键的表,比如专业表)
      外码1
      外码2
      外码2
  3. 用户定义的完整性

⭐码与相关概念#

  1. 超码#

    能 唯一标识 元组的任意属性集。

    • 关系:候选码是最小超码,候选码的任意超集都是超码。
    • 例如:S(Sno, Sdept, Sage),若:Sno → Sno, Sdept, Sage,
      {Sno}是候选码 也是超码,(Sno, Sdept)也是超码。
  2. 候选码(码)#

    能够唯一标识元组的 最小 超码
    {Sno}是候选码

  3. 全码#

    如果关系的所有属性U才是R的码,称为全码
    例:R(P,W,A)R(P,W,A)(演奏者、作品、听众),全码为 (P,W,A)(P,W,A)。P, W, A 都是关系R的主属性,在关系R中没有非主属性

  4. 主码#

    如果一个关系模式有多个候选码,则从中选一个作为主码。

    • 例如:Student(Sno, Sname, Ssex, Sage, Sdept):Sno 是候选码,Sname 也是候选码。
        → 可以选 Sno 作为主码。
  5. 外码#

    R中 XX 不是当前关系R的码,是另一关系的码,则 XX 为 RR 的外码。
    例:SC(<u>Sno,Cno</u>,Grade)SC(<u>Sno,Cno</u>,Grade) 中 Sno不是码,Sno是 S(Sno,Sdept,Sage)的码,
      则 SnoSno 是 SCSC 的外码
      FOREIGN KEY (deptno) REFERENCES Department(deptno)
      外码的作用是:建立表之间的引用联系,并保证参照完整性。

  • 主属性与非主属性#

    • 主属性:包含在任意候选码中的属性。
    • 非主属性:不包含在任何候选码中的属性。

例#

  1. S(Sno, Sdept, Sage)中,只有一个候选码Sno,所以:Sno是关系S的主属性,Sdept和Sage是关系S的2个非主属性;
  2. SC(Sno, Cno, Grade)中,只有一个候选码(Sno, Cno),所以:Sno和Cno是关系SC的2个主属性,Grade是关系SC的非主属性。
  3. Student(Sno, Sname, Ssex, Sage, Sdept)中。有两个候选码:Sno 和 Sname(不允许学生同名),所以:Sno 和 Sname是关系Student的2个主属性,Ssex, Sage, Sdept是关系Student的3个非主属性。

⭐C2 关系代数#

  • 关系运算 img_7.png 选择:σSdept=′IS′(Student)\sigma_{Sdept='IS'}(Student)、投影:πSdept(Student)\pi_{Sdept}(Student)

  • 例

    • 所有:除运算÷;只: alt text alt text
  • 最大/最高 在当前的所有顾客中, 查询折扣(discnt)最高的顾客的编号?
    令 S:=CS := C, “折扣不是最高的顾客的id”
    R2:=πC.cid(σC.discnt<S.discnt(C×S))R_2 := \pi_{C.cid}(\sigma_{C.discnt < S.discnt}(C \times S))
    答案是 πcid(C)−πC.cid(σC.discnt<S.discnt(C×S))\pi_{cid}(C) - \pi_{C.cid}(\sigma_{C.discnt < S.discnt}(C \times S))

    象集1
    象集1
    象集1
    象集1
  • 象集
    该属性值对应的其他属性值集合,相当于除法

    象集1
    象集2
  1. 除运算 R ÷ S
    找出R、S中相同的属性列(B、C),R 剩下的属性列(A) 作为结果。
    aia_i的所有(bjb_j,cjc_j),aia_i包含S中的全部b、c。

    外连接
    左右外连接
    • 例
    象集1
    象集1

⭐C3 SQL#

  • SQL基本执行结构
    SELECT ...
    FROM ...
    WHERE ...
    GROUP BY ...
    HAVING ...
    ORDER BY ...;
    逻辑顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

对查询结果的处理#

  1. 对查询结果排序 ORDER BY 属性名 ASC升序/DESC降序 查询选修了3号课程的学生的学号及其成绩,查询结果按分数降序排列。
    SELECT Sno, Grade
    FROM SC
    WHERE Cno= '3'
    ORDER BY Grade DESC;
  2. 对查询结果分组 GROUP BY/HAVING 求各个课程号及相应的选课人数。
    SELECT Cno,COUNT(Sno)
    FROM SC
    GROUP BY Cno; // 按照Cno为一行,相同Cno为一行
    查询选修了3门以上课程的学生学号。
    SELECT Sno
    FROM SC
    GROUP BY Sno
    HAVING COUNT(*) >3;
    • 辨析 HAVING 与 WHERE:只有HAVING可以用AVG()等 alt text

基本表 TABLE#

  1. 定义基本表
    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限制 / 拒绝数据库直接禁止父表的改/删操作
  2. 修改基本表(ALTER TABLE)

    • ADD新增列/完整性约束
      ALTER TABLE Student
      ADD COLUMN S_email VARCHAR(50) NOT NULL UNIQUE; -- 增加邮箱列,要求非空且唯一
      ADD CONSTRAINT chk_sex CHECK (Ssex IN ('男', '女')); -- 为 Ssex 增加 CHECK 约束(性别只能是男 / 女)
    • DROP 删除列
      ALTER TABLE Student
      DROP COLUMN S_email CASCADE; --删除“邮箱”列,默认RESTRICT
      DROP CONSTRAINT chk_sex; -- 删除性别CHECK约束
    • 修改列的定义
      ALTER TABLE Student
      ALTER COLUMN Sage INT; -- 修改数据类型:年龄从SMALLINT改为INT
      ALTER COLUMN Sname CHAR(30); -- 修改列长度(姓名从 CHAR (20) 改为 CHAR (30))
      ALTER TABLE Course ADD UNIQUE(Cname); -- 增加课程名称必须取唯一值的约束条件
  3. 删除基本表

    DROP TABLE <表名>[RESTRICT| CASCADE];
    • RESTRICT:只有在该表没有被其他数据库对象依赖时,才能删除。
    • CASCADE:删除该表的同时,还会删除依赖于该表的相关对象或依赖关系

视图 VIEW#

  1. 视图的作用?
    简化复杂查询;
    为不同用户提供不同数据视图;
    隐藏敏感属性,提高安全性;
    阻止不符合条件的数据加入/修改成不符合条件的数据。
  2. 定义视图 AS
    CREATE VIEW S_G(Sno,Gavg) -- 可选:列名
    AS
    SELECT Sno,AVG(Grade)
    FROM SC
    GROUP BY Sno;
  3. 查询视图
  4. 更新视图
  5. 删除视图 DROP VIEW S_G [RESTRICT | CASCADE]

  1. 字符串匹配
    • ⭐字符匹配 LIKE/=
    SELECT* FROM Student WHERE Sno LIKE '24%' -- 通配符,查询学号以24开头的学生的详细信息
    SELECT* FROM Student WHERE Sno = '24%' -- 查询学号就是'24%'学生的详细信息
    • ⭐字符串中的通配符
    SELECT Sname, Sno, Ssex
    FROM Student
    WHERE Sname LIKE '刘%'; -- 查询所有姓刘学生的姓名、学号和性别
    SELECT Sname
    FROM Student
    WHERE Sname LIKE '欧阳_'; -- 查询姓"欧阳"且全名为三个汉字的学生的姓名
    SELECT Sname, Sno
    FROM Student
    WHERE Sname LIKE '_阳%'; -- 查询名字中第2个字为"阳"字的学生的姓名和学号
    SELECT Sname, Sno, Ssex
    FROM Student
    WHERE Sname NOT LIKE '刘%'; -- 查询所有不姓刘的学生姓名、学号和性别
  2. 多表连接 JOIN
    SELECT DISTINCT s.sid, s.sname
    FROM Student s
    JOIN Enroll e ON s.sid = e.sid
    JOIN Course c ON e.cid = c.cid
    WHERE c.cname = '数据管理基础'
    AND c.dept = '智软学院';
    使用 DISTINCT 是为了消除同一学生由于多条匹配记录产生的重复
    • DISTINCT 的位置:SELECT 子句;聚集函数内部。
      SELECT DISTINCT dept
      FROM Student;
      COUNT(DISTINCT sid)
      AVG(DISTINCT score)
      SUM(DISTINCT amount)
  3. 分组与 HAVING先Group 分组后 HAVING
    分组
  4. EXISTS 子查询有结果,返回TRUE;子查询无结果,返回FALSE
    SELECT Sname
    FROM Student S
    WHERE EXISTS (
    SELECT *
    FROM SC
    WHERE SC.Sno = S.Sno
    AND Cno = '1' -- 从 Student 表里,找出那些‘存在’选修 1 号课程记录的学生
    ); -- 查询选修了 1 号课程的学生
    SELECT Sname
    FROM Student S
    WHERE NOT EXISTS (
    SELECT *
    FROM SC
    WHERE SC.Sno = S.Sno
    AND Cno = '1'
    ); -- 查询没选1号课程的学生
    • 查询选修全部课程的学生
      SELECT Sname
      FROM Student
      WHERE NOT EXISTS ( -- ③ 除去没有选择课程的学生
      SELECT *
      FROM Course -- ② 有NOT EXISTS学生选择的课程中的:学生没有选择的课程
      WHERE NOT EXISTS (
      SELECT *
      FROM SC
      WHERE SC.Sno = Student.Sno
      AND SC.Cno = Course.Cno -- ① 对SC中的每门课程,去Student、Course中寻找有无记录
      ) -- 学生选择的课程
      );
      ✅ 理解:不存在一门课是这个学生没选的
    • 例
    exists
    exists
    exists
    exists

⭐例题#

  1. 所有课程都及格:HAVING MIN(grade)>=60
    分组
  2. 修完本院所有必修课
    分组
  3. 每组最大值
    分组
  4. 只讲授本院课程

⭐C4 数据库编程#

方式怎么使用主要场景
交互式 SQL用户直接输入 SQL临时查询、数据库管理
嵌入式 SQL主语言+SQLSQL与C、C++结合
过程化 SQLSQL+变量、判断、循环存储过程、函数、触发器

嵌入式SQL#

  1. 主变量:能在SQL中使用的主语言变量
    • 必须在声明区声明
    -- 声明区
    EXEC SQL BEGIN DECLARE SECTION;
    char givensno[10];
    char hsname[21];
    int hsage;
    EXEC SQL END DECLARE SECTION;
    -- 声明区
    EXEC SQL select Sno, Cno, Grade
    into :hsno, :hcno, :hgrade, :gradeid -- 指示变量
    from Student
    where Sno = :givensno ; -- 查找给定学号的学生的信息
  2. 指示变量 gradeid:说明成绩是否为空
    取值含义
    0返回的是正常非空值
    大于 0字符串被截断
    -1数据库值为 NULL
    • 用指示变量写入NULL:把成绩置为空
      gradeIndicator = -1;
      EXEC SQL UPDATE SC
      SET Grade = :hgrade :gradeIndicator -- 实际写入的是 grade = NULL
      WHERE Sno = :hsno
      AND Cno = :hcno;
  3. 通信区 SQLCA:记录每条SQL执行后的状态信息
    SQLCODE = 0:执行成功;
    非0:没有找到数据、警告或者错误。
    if (SQLCA.SQLCODE != 0) {
    break;
    } -- 前一条执行SQL语句,然后检查:若不为0 → 退出
  4. ⭐游标 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,密码是 MANAGER
    EXEC SQL DECLARE SX CURSOR FOR /*定义游标SX*/
    SELECT Sno,Sname,Ssex,Sage /*游标SX对应的查询语句*/
    FROM Student
    WHERE 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 Student
    SET Sage = :NEWAGE -- Sage更新为读入的NWEAGE
    WHERE 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:添加异常处理👇

异常处理#

  1. whenever是嵌入式SQL的全局异常捕获机制
    EXEC SQL WHENEVER 条件 动作;
    预编译器在处理过程中,会根据WHENEVER语句的定义,在每一条嵌入式SQL语句之后添加下面的异常状态检查命令: if ( condition ) { action }
  2. 常见执行异常
    SQLERROR、NOT FOUND、SQLWARNING
  3. 处理方法 CONTINUE、GOTO label、STOP、DO function | BREAK | CONTINUE
  4. 例
    exec sql whenever sqlerror goto report_error ; -- SQL执行错误
    exec sql whenever not found goto notfound ; -- 查询/FETCH没有得到记录,用于结束游标循环

动态SQL#


过程化SQL#


⭐触发器#

  1. **什么是触发器?**当某张表发生插入、删除或修改时,数据库自动执行一段事先写好的程序。
    学生成绩提高超过10%时,自动记录日志;
    插入教师信息时,如果教授工资低于4000,自动改成4000;
    删除数据时,自动备份被删除的数据。

  2. 格式 只要修改就会触发,满足条件才执行后续操作

    CREATE TRIGGER 触发器名
    BEFORE | AFTER
    INSERT | UPDATE | DELETE(OR 其他操作)
    ON 表名
    FOR EACH ROW
    BEGIN
    -- 触发后自动执行的语句
    END;
    ……
  • 例1:成绩提高10%时记录日志
    CREATE TRIGGER SC_T -- 创建名为 SC_T 的触发器
    AFTER UPDATE OF Grade ON SC -- SC 中的 Grade 修改完成后,触发该触发器
    REFERENCING
    OLD 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_U
    OldTuple.Sno,
    OldTuple.Cno,
    OldTuple.Grade,
    NewTuple.Grade
    );
  • 例2:教授工资不能低于4000
    插入/修改教师信息时,如果该教师是教授,并且工资低于4000,就自动把工资改成4000。
    CREATE TRIGGER Insert_Or_Update_Sal
    BEFORE INSERT OR UPDATE ON Teacher
    REFERENCING NEW ROW AS newTuple
    FOR EACH ROW
    BEGIN
    IF newTuple.Job = '教授'
    AND newTuple.Sal < 4000 THEN
    newTuple.Sal := 4000;
    END IF;
    END;

函数#

  1. IS:声明部分:接下来存放查询结果的 局部变量、常量、游标等
  • 例:计算总输入
    CREATE OR REPLACE FUNCTION totalsal -- 创建/替换一个名为totalsal的函数
    (
    v_empno IN emp.empno%TYPE -- 调用者输入
    )
    RETURN NUMBER -- 函数最终返回一个 NUMBER 类型的数值
    IS
    totalsal1 NUMBER;
    BEGIN
    SELECT sal + comm
    INTO totalsal1
    FROM emp
    WHERE empno = v_empno;
    -- 把v_empno编号的员工的 sal工资+comm奖金,结果存入totalsal1
    RETURN totalsal1;
    END;

存储过程#

alt text

  1. 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;
  2. IN 调用者传入
    CREATE OR REPLACE PROCEDURE TRANSFER (
    inAccount IN Account.AccountNum%TYPE, -- 转入账户:IN调用者传入
    outAccount IN Account.AccountNum%TYPE, -- 转出账户
    amount IN NUMBER -- 转账金额
    )
  3. INTO 查询并保存到局部变 → 异常处理
    SELECT AccountNum
    INTO inAccountNum
    FROM Account
    WHERE AccountNum = inAccount
    FOR UPDATE;
    EXCEPTION
    /* SELECT INTO没有查询到账户 */
    WHEN NO_DATA_FOUND THEN
    ROLLBACK;
    RAISE_APPLICATION_ERROR(
    -20004,
    '转入账户或转出账户不存在'
    );
    /* 处理其他异常 */
    WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
  • 例:转账存储过程
    /*====================================================
    1. 创建账户基本表
    ====================================================*/
    CREATE TABLE Account (
    AccountNum VARCHAR2(11) PRIMARY KEY, -- 账户号
    Total NUMBER(15, 2) DEFAULT 0 NOT NULL, -- 账户余额
    CONSTRAINT CK_Account_Total
    CHECK (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, Total
    FROM Account
    ORDER 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 THEN
    RAISE_APPLICATION_ERROR(
    -20001,
    '转账金额必须大于0'
    );
    END IF;
    /* 转入账户和转出账户不能相同 */
    IF inAccount = outAccount THEN
    RAISE_APPLICATION_ERROR(
    -20002,
    '转入账户和转出账户不能相同'
    );
    END IF;
    /*------------------------------------------
    2. 查询并锁定转出账户
    ------------------------------------------*/
    SELECT Total
    INTO totalDepositOut -- 查询并保存到局部变量totalDepositOut
    FROM Account
    WHERE AccountNum = outAccount
    FOR UPDATE; -- 加行级锁。防止转账过程中,其他事务同时修改该账户余额。
    -- 一直到COMMIT/ROLLBACK
    /*------------------------------------------
    3. 检查转出账户余额:小于转账金额 抛出ERROR
    ------------------------------------------*/
    IF totalDepositOut < amount THEN
    RAISE_APPLICATION_ERROR(
    -20003,
    '转出账户余额不足'
    );
    END IF;
    /*------------------------------------------
    4. 检查并锁定转入账户
    ------------------------------------------*/
    SELECT AccountNum
    INTO inAccountNum
    FROM Account
    WHERE AccountNum = inAccount
    FOR UPDATE;
    /*------------------------------------------
    5. 转出账户扣款
    ------------------------------------------*/
    UPDATE Account
    SET Total = Total - amount
    WHERE AccountNum = outAccount;
    /*------------------------------------------
    6. 转入账户加款
    ------------------------------------------*/
    UPDATE Account
    SET Total = Total + amount
    WHERE AccountNum = inAccount;
    /*------------------------------------------
    7. 提交转账事务
    ------------------------------------------*/
    COMMIT;
    EXCEPTION
    /* SELECT INTO没有查询到账户 */
    WHEN NO_DATA_FOUND THEN
    ROLLBACK;
    RAISE_APPLICATION_ERROR(
    -20004,
    '转入账户或转出账户不存在'
    );
    /* 处理其他异常 */
    WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
    END TRANSFER;

静态SQL vs 动态SQL#


C5 关系数据规范化理论#

⭐数据依赖:函数依赖#

  1. 定义
    X确定唯一Y,则称“X函数确定Y”或“Y函数依赖于X”,记作 X→YX \to Y。
    • XX:决定因素
    • YY:依赖因素
    函数依赖例1

1. 平凡/非平凡函数依赖#

  • 平凡函数依赖:X→YX \to Y 且 Y⊆XY \subseteq X(Sno本来就在左边) 例:(Sno,Cno)→Sno(Sno,Cno) \to Sno,(Sno,Cno)→Cno(Sno, Cno) → Cno
  • 非平凡函数依赖:X→YX \to Y 且 Y⊈XY \nsubseteq X 例:(Sno,Cno)→Grade(Sno,Cno) \to Grade

2. 完全/部分函数依赖#

  • 完全函数依赖:要决定Y,X的所有部分都要用上,如要确定一门课的成绩,需要同时知道学号+课程名
    例:(Sno,Cno)→FGrade(Sno,Cno) \stackrel{F}{\to} Grade
  • 部分函数依赖:要决定Y,只需要X中的一部分属性就可以,如确定专业,只需知道Sno就行,Cno是不必要的
    例:(Sno,Cno)→PSdept(Sno,Cno) \stackrel{P}{\to} Sdept

3. 传递函数依赖#

  • 定义:若 X→YX \to Y,Y↛XY \nrightarrow X,Y→ZY \to Z(Y⊈XY \nsubseteq X Z⊈YZ \nsubseteq Y),则 X→传递ZX \stackrel{\text{传递}}{\to} Z
  • 例:Sno→SdeptSno \to Sdept,Sdept→MnameSdept \to Mname ⇒\Rightarrow Sno→传递MnameSno \stackrel{\text{传递}}{\to} Mname

数据依赖:多值依赖#


范式 & 规范化#

  1. 范式(NF):是对一个关系中允许存在的数据依赖的要求
    规范化:通过投影分解将低范式转换为高范式。
  2. 范式等级(从低到高包含关系): 1NF⊃2NF⊃3NF⊃BCNF⊃4NF⊃5NF1NF \supset 2NF \supset 3NF \supset BCNF \supset 4NF \supset 5NF
  3. 规范化:将低范式通过模式分解转为高范式,消除不良数据依赖
    范式越高,对表中数据依赖的要求越严格。
范式主要解决的问题一句话理解
1NF属性不可再分每个格子只能放一个值
2NF部分函数依赖非主属性不能由主属性部分函数依赖推出,必须完全依赖
3NF传递函数依赖不存在非主属性对候选码的传递函数依赖
BCNF决定因素不是码所有非平凡函数依赖中的决定因素(左边),都必须是包含候选码 (是超码)
4NF-每个非平凡多值依赖 X →→ Y 都满足 X含有码

1NF:属性原子性;2NF:不存在主属性部分函数依赖非主属性;
3NF:不存在 非主属性 对 候选码的传递依赖;BCNF:左边都是 超码
部分主 → 非主;非主 ⇒传递\xRightarrow{传递} 候选码;超码 →

二、1NF(第一范式)#

  1. 定义#

    关系模式的所有属性均为 不可再分的原子数据项,是关系数据库的最低要求。

  2. 实例#

    关系模式 SLC(Sno, Sdept, Sloc, Cno, Grade) 中,所有属性均为原子属性,满足 1NF。

三、2NF(第二范式)#

  1. 定义
    非主属性不能由候选码部分函数依赖推出,必须完全依赖。
    • 如:AF 是候选码,D 是非主属性。
      若A → D,说明非主属性 D对候选码部份依赖 ⇒\Rightarrow 不满足2NF
  2. 实例:SLC 关系
    函数依赖集1
  3. Solution
    分解成2个关系模式(但插入异常、删除异常任然存在)
    函数依赖集1

四、3NF(第三范式)#

  1. 定义
    不存在非主属性对候选码的传递函数依赖
    码 不能和 非主属性 有传递依赖关系
    不存在码X、属性Y、非主属性Z使得 X→Y,Y→ZX→Y,Y→Z 成立 ⇒Y↛X\Rightarrow Y \nrightarrow X

  2. 实例:SL 关系(Sno是码) alt text

五、BCNF(巴斯范式)#

  1. 定义
    所有非平凡函数依赖中的决定因素(左边),都必须是包含候选码(都必须是超码)
  2. 实例 1:仓库管理关系:把违反的(管理员,仓库名)单独成表
    BCNF1
    BCNF1

4NF(第四范式)#

  1. 定义
    每个非平凡多值依赖 X →→ Y 都满足 X含有码
  2. 分解
    把独立的多值事实拆开

⭐Armstrong公理系统#

  1. Armstrong公理系统 - 基本规则#

    1. A1 自反律:知道一组属性,一定知道部分
         {Cno, Sname} → Cno
    2. A2 增广律:若 X→YX \rightarrow Y,则 XZ→YZXZ \rightarrow YZ
         Cno → Sname ⇒\Rightarrow {Cno, clsno} → {Sname, clsno}
    3. A3 传递律:若 X→YX\rightarrow Y 且 Y→ZY\rightarrow Z,则 X→ZX\rightarrow Z
         Cno → Class、Class → Teacher ⇒\Rightarrow Cno → Teacher
  2. Armstrong公理系统 -扩充规则#

    1. 合并规则:若 X →\to Y 且 X →\to Z,则 X → YZ
    2. 分解规则:若:X → YZ,则:X → Y,X → Z
    3. 伪传递规则:若 X →\to Y 且 WY →\to Z,则 XW →\to Z
    • 引理:X→A1A2⋯Ak⇒X→Ai(i=1,2,…,k)X \to A_1A_2\cdots A_k \Rightarrow X \to A_i( i=1,2,\dots,k) 成立
      学号 → (姓名, 班级, 专业) ⇒\Rightarrow 学号 → 姓名,学号 → 班级,学号 → 专业
  3. 函数依赖的逻辑蕴涵#

    • F 的所有规则,必然导致 X→Y 成立(X Y是属性/属性集)。是由F带来的,没有写出但必然成立的规则。
      则称:F 逻辑蕴涵 X → Y,记作 F ⊨ X → Y
    • 例:F = {学号 → 系名, 系名 → 系主任 },则:F ⊨ 学号 → 系主任
  4. 函数依赖集的覆盖 & 等价#

    • 核心概念
      • 覆盖:若 F 能推出 G 里的所有依赖 (G⊆F+G\subseteq F^+) ,就说 F覆盖G
      • 等价:若 F覆盖G 且 G覆盖F(F+=G+F^+ = G^+),就说 F和G等价(充要关系)
    • 例
      设:F = { A → B, A → C },设:G = { A → BC }
      ⇒\Rightarrow F 和 G 等价。
      因为:由 F 可用合并规则推出 A → BC;由 G 可用分解规则推出 A → B 和 A → C
      所以:F⁺ = G⁺(“分开写”和“合起来写”本质上表达的是同一件事)
  5. 函数依赖集#

    函数依赖:A→B 从A出发能确定唯一B。即一对一/多对一(不能出现多对一/多对多)
      A→B 多对一/AC→D 一对一;但是A→B 多对一/A→C 多对多

    函数依赖集1
    函数依赖集2
  6. 3.1 函数依赖集的闭包 F^+#

    • 定义:F+={X→Y∣F⊨X→Y}F^+ = \{ X \to Y \mid F \vDash X \to Y \},即:所有被 F 逻辑蕴涵的函数依赖的集合。
    • 属性集 U={A,B,C},函数依赖集 F={A→B, B→C} → F+F^+ ={ 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⁺ 👇来判断某个依赖是否成立。 ​
  7. 3.2 属性集的闭包 AF+A_F^+#

    • 根据 F,属性集 A 最终能够确定哪些属性,AF+A_F^+称为属性A关于函数依赖集F的闭包。

    • ⭐例:计算A关于F的闭包 AF+A_F^+(迭代算法)
      初始:AF+=AA_F^+ = {A}   用 A→B:AF+=A,BA_F^+ = {A,B}   用 B→C:AF+=A,B,CA_F^+ = {A,B,C}

    属性集的闭包
    - ⭐例:求候选码 若A能推导出所有属性 → A是候选码
  8. 3.3 极小函数依赖集 / 最小依赖集 / 最小覆盖:F的最简形式#

    • 需满足 3 个条件:#

      设 F 为函数依赖集,FmF_m 是其极小依赖集,需满足:

      1. 右边只有一个属性
      2. 左边没有多余属性
        AB → C,如果:A⁺ 中已经包含 C说明只用 A 就能推出 C,B 是多余的。
        于是:AB → C可以简化为:A → C
      3. 没有冗余函数依赖
    • 计算算法#

      1. 右部拆分:AD → EF 拆成:AD → E,AD → F;
      2. 删左边多余属性:对于 ADF→ C:∵AD → F ,故可简化为 AD → C;
        对于 ADF → D:∵D已经包含在左边 →\rightarrow 可以删除该平凡函数依赖;
      3. 删冗余依赖:对于C → B,可以由C → F,F → B推出。 删除 C → B;
      • 特性:每个函数依赖集都等价于一个极小依赖集,且不唯一。

🌟例题#

  • ⭐例1:属性集闭包算法 -计算A关于F的闭包 AF+A_F^+#


    初始:AF+=AA_F^+ = {A}   用 A→B:AF+=A,BA_F^+ = {A,B}   用 B→C:AF+=A,B,CA_F^+ = {A,B,C}

    • 计算方法:迭代算法
      属性集的闭包
  • ⭐例:等价/最小函数依赖集 -属性集闭包算法#

    属性集的闭包
  • ⭐例2:极小函数依赖集算法#

    1. 右部拆分:AD → EF 拆成:AD → E,AD → F;
    2. 删左边多余属性:对于 ADF→ C:∵AD → F ,故可简化为 AD → C;
      对于 ADF → D:∵D已经包含在左边 →\rightarrow 可以删除该平凡函数依赖;
    3. 删冗余依赖:对于C → B,可以由C → F,F → B推出。 删除 C → B;
  • ⭐(补充3)例:判断冗余依赖#

    1. 函数依赖集 M = {C → E,E → B,BCD → A},断言:BCD → A 可以简化成 CD → A,它是一个冗余函数依赖
      构造 N = {C → E,E → B,CD → A},只需证明:M+=N+M^+ = N^+(M能推出的=N能推出的)
      ✅只需证明:M ⊨ CD → A? N ⊨ BCD → A?

      ① 证明:M ⊨ CD → A
      只需证明 C,DM+{C,D}_M^+ 中包含 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

    2. 函数依赖集 M = {B → D,D → E,C → F,BC → A,EF → A},断言:BC→ A 是一个冗余的函数依赖
      构造 N = {B → D,D → E,C → F,EF → A},要证明 M+=N+M^+ = N^+,
      只需证明:N ⊨ BC → A,即:BCN+{BC}_N^+ 包含A

    3. 函数依赖集 F = {A → B,B → A,AB → C},判断 AB → C 是否为部分函数依赖?
      只需假设把AB → C 换成 A → C/B → C是否依然成立。同上。

  • ⭐例3:计算候选码#

    要求候选码K,只需找到能推出全部属性U的 KR+=UK_R^+=U 即可。

    1. 没有出现在右边的属性 → 一定在候选码内(因为没有属性能推出它)
    2. 只出现在右边的属性 → 一般不放在候选码(因为可以由其他属性推出)
    3. 其他属性待选跟1结合
    • 先算1必须出现在候选码中的属性K,是否有 KR+=UK_R^+ = U
    • 再依次跟3中属性结合。候选码可能不止1个。

⭐模式分解#

  1. 模式分解的三个理解#

    1. 无损连接:自然连接后完全等于原表
    2. 保持函数依赖:分解后所有依赖规则都能找到
    3. 既 无损连接 又 保持函数依赖
  2. ⭐无损连接#

    • 有损连接:会产生多余元组。

    • 定义:R = R1 ⋈ R2 ⋈ … ⋈ Rn,能 自然连接⋈\bowtie 回来,完全等于原表。

    • 💫判定:两张表的公共属性,如果能推出某一张表的非公共属性,就是无损连接。
      例如1:设有一个关系模式T(A,B,C),函数依赖集F, 请判断下述的 分解ρ=T1,T2ρ={T_1,T_2}是否具有无损连接性?A是公共属性,能推出T1的非公共属性B。故满足无损连接性

      函数依赖集分解ρ是否满足无损连接性?解释
      {A→B}T1(A,B)T2(A,C)T_1(A,B) T_2(A,C)✅A是公共属性,能推出B
      {A→C,B→C}T1(A,B)T2(A,C)T_1(A,B) T_2(A,C)✅A是公共属性,能推出C
      {A→B}T1(A,B)T2(B,C)T_1(A,B) T_2(B,C)❌B是公共属性,不能推出非公共属性
      {A→B,B→C}T1(A,C)T2(B,C)T_1(A,C) T_2(B,C)}❌C是公共属性,不能推出非公共属性
  3. ⭐保持函数依赖#

    分解后,原来的函数依赖能在子关系中直接检查。


🌟3NF模式分解#

  1. 目标:得到 满足3NF 且 既有无损连接性又保持函数依赖 的分解
  2. 步骤:每条依赖建一张表,最后检查有没有候选码,没有就补上
    1. 求 F 的极小函数依赖集 Fmin
    2. 求候选码
    3. 谁决定谁,就把它们放在一张表里。
      例如:Sno → Sdept就建:SD(Sno, Sdept)
    4. 合并左边相同的
    5. 💫检查是否需要补候选码表:如果没有任何子关系包含原关系的候选码 K,则补一个关系 R(K)
  • 💫例:到3NF的模式分解示例 alt text

🌟BCNF 模式分解#

  1. 目标:分解到 BCNF,并且保证无损连接。
  2. 步骤:
    1. 初始 ρ = {R}
    2. 检查每个关系是否满足 BCNF
    3. 如果某个关系不满足 BCNF,则存在:X → Y,且 X 不是超码
    4. 分解为: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}减去公共属性
       → R2R_2 = U-(Y-X) = {A,B,D,E},R1R1={A,B,C}
    5. 继续检查,直到全部满足 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)


多值依赖#

  1. 定义:一个属性决定的不是一个值,而是一组值。记作 X →→ Y,读作Y多值依赖于X(有其他属性Z,Y、Z都多值依赖于X的充要条件是 X、Y可以自由组合)
  2. 多值依赖 vs 函数依赖
    1. 跟Z的关系
      函数依赖:只问“X 能不能唯一确定 Y”。
      多值依赖:还要问“Y 和剩下的属性是不是独立”。
    2. 跟子集的关系
      A → BC,则:A → B A → C
      多值依赖不一定成立

⭐C6 数据库的安全性#

  1. 授权:授予GRANT 与 回收REVOKE
    • 授予 GRANT
      GRANT SELECT,INSERT
      ON Student
      TO U1 -- 把对Student的查询、插入权限授予用户U1
      GRANT ALL PRIVILIGES
      ON TABLE Student,Course
      TO U2,U3 -- 把对Student表和Course表的全部权限授予用户U2和U3
      转授 WITH GRANT OPTION
      GRANT SELECT
      ON Student
      TO user2
      WITH GRANT OPTION;
    • 回收 REVOKE
      REVOKE UPDATE(Sno) ON STUDENT FROM U4 -- 把用户U4修改学生学号的权限收回
      REVOKE SELECT ON SC FROM PUBLIC -- 收回所有用户对表SC的查询权限

⭐C7 数据库设计#

  1. 数据库设计基本步骤
数据库的设计
工作所属阶段
收集业务需求、数据流需求分析
画 ER/EER 图概念结构设计
ER 转关系模式、规范化逻辑结构设计
设计索引、文件组织、聚簇物理结构设计
建表、装载数据、编程数据库实施

⭐C8 ER/EER模型#

  1. 父子关系-ISA联系:不相交 & 完全特化(超类&子类)
    不相交约束 x:不能是多个
    完全特化 ||:必须是一个 alt text alt text

  2. Part-of联系 双菱形 alt text

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

  4. 基数约束表示:min(强制参与/非强制参与)..max(单值参与/多值参与)
    左边基数:一个左边,对应多少个右边

    • 为什么引入:消除歧义
  5. 组合属性 / 多值属性

    组合属性的子属性表示
    多值属性的双线桥表示
    多值属性的基数约束表示


C9 数据库恢复技术#

事务 & ACID特性#

  1. 什么是事务?:原子操作,事务是一个不可分割的数据库操作序列:
  2. ACID四个特性
    特性含义例子主要由什么保证
    Atomicity 原子性要么全做,要么全不做扣款和加款必须一起完成UNDO、ROLLBACK、日志
    Consistency 一致性从一个一致状态变为另一个一致状态转账前后总金额不变正确事务程序、完整性约束、并发与恢复机制
    Isolation 隔离性并发事务之间互不干扰别人不能看到只扣款、未加款的中间状态第11章并发控制、封锁
    Durability 持久性提交后的结果不会因故障丢失转账提交后,断电也不能丢失REDO、日志、转储

COMMIT、ROLLBACK & 保存点 SAVEPOINT#

alt text 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

数据转储#

  1. 静态转储 & 动态转储
  2. 海量存储 & 增量存储

⭐日志文件#

  1. **什么是日志文件?**日志文件记录事务对数据库的更新操作。
  2. 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
数据库复习
https://lilyly9.github.io/posts/数据库复习/
作者
Stari
发布于
2026-06-27
许可协议
CC BY-NC-SA 4.0