数据库系统原理 (二): 关系数据库

参考《数据库系统概论》

关系数据结构及形式化定义

关系

  • 关系模型的数据结构非常简单,只包含单一的数据结构一一关系
    • 从用户角度,关系模型中数据的逻辑结构是一张二维表
  • 现实世界的实体以及实体间的各种联系用关系来表示

  • 关系模型是建立在集合代数的基础上的,这里从集合论角度给出关系数据结构的形式化定义

(Domain)

  • 域是一组具有相同数据类型的值的集合
    • 例如, 整数、实数…
  • 域中所含元素个数为基数 (Cardinal number)

笛卡尔积(Cartesian Product)

  • 给定一组域 D 1 , D 2 , … , D n D_1,D_2,…,D_n D1D2Dn,这些域中可以有相同的。 它们的笛卡尔积为:
    D 1 × D 2 × … × D n = { ( d 1 , d 2 , … , d n ) ∣ d i ∈ D i , i = 1 , 2 , … , n } D_1×D_2×…×D_n =\{(d_1,d_2,…,d_n)|d_i∈D_i,i=1,2,…,n\} D1×D2××Dn{ (d1d2dn)diDii12n}
    • 其中每一个元素 ( d 1 , d 2 , … , d n ) (d_1,d_2,…,d_n) (d1d2dn) 叫作一个 " n n n 元组 ( n n n-tuple) 或 简称元组 (Tuple)
    • 元素中的每一个值 d i d_i di 叫作一个分量 (Component)
    • D i ( i = 1 , 2 , … , n ) D_i(i=1,2,…,n) Di(i12n) 为有限集,其基数为 m i m_i mi i = 1 , 2 , … , n i=1,2,…,n i12n),则 D 1 × D 2 × … × D n D_1×D_2×…×D_n D1×D2××Dn基数 M M M 为 (笛卡儿积的基数对应元组个数):
      M = ∏ i = 1 n m i M=\prod_{i=1}^n m_i M=i=1nmi
  • 笛卡尔积的表示方法
    • 笛卡尔积可表示为一个二维表,表中的每行对应一个元组,表中的每列对应一个域
      在这里插入图片描述

可以看到,笛卡尔乘积中很多元素都是没有意义的,所以进一步提出了关系的概念

关系(Relation)

  • D 1 × D 2 × … × D n D_1×D_2×…×D_n D1×D2××Dn 的子集叫作在域 D 1 , D 2 , … , D n D_1,D_2,…,D_n D1D2Dn 上的关系,表示为
    R ( D 1 , D 2 , … , D n ) R(D_1,D_2,…,D_n) R(D1D2Dn)
    • R R R关系名
    • n n n:关系的(Degree)(属性个数)
      • n = 1 n=1 n=1时,称该关系为单元关系(Unary relation)或一元关系
      • n = 2 n=2 n=2 时,称该关系为二元关系(Binary relation)
  • 按照笛卡儿积的定义, 关系可以是一个无限集合。由于笛卡尔积不满足交换律,所以按照数学定义, ( d 1 , d 2 , . . . , d n ) ≠ ( d 2 , d 1 , . . . , d n ) (d_1,d_2,...,d_n)\neq(d_2,d_1,...,d_n) (d1,d2,...,dn)=(d2,d1,...,dn). 当关系作为关系数据模型的数据结构时,需要给予如下的限定和扩充:
    • (1) 无限关系在数据库系统中是无意义的。因此,限定关系数据模型中的关系必须是有限集合
    • (2) 通过为关系的每个列附加一个属性名的方法取消关系元组的有序性,即 ( d 1 , . . . , d i , d j , . . . , d n ) = ( d 1 , . . . , d j , d i , . . . , d n )     i , j = 1 , 2 , . . . , n (d_1,...,d_i,d_j,...,d_n)=(d_1,...,d_j,d_i,...,d_n)\ \ \ i,j=1,2,...,n (d1,...,di,dj,...,dn)=(d1,...,dj,di,...,dn)   i,j=1,2,...,n
  • 关系也是一个二维表,表的每行对应一个元组,表的每列对应一个域
    • 关系中的每个元素是关系中的元组,通常用 t t t 表示
    • 关系中不同列可以对应相同的域,为了加以区分,必须对每列起一个名字,称为属性(Attribute)。 n n n 目关系必有 n n n 个属性
    • 候选码(Candidate key): 若关系中的某一属性组的值能唯一地标识一个元组,则称该属性组为候选码
    • 全码(All-key): 最极端的情况:关系模式的所有属性组是这个关系模式的候选码,称为全码
    • 主码 (Primary key): 若一个关系有多个候选码,则选定其中一个为主码
    • 主属性 (Prime attribute): 候选码的诸属性称为主属性. 不包含在任何侯选码中的属性称为非主属性( Non-Prime attribute)或非码属性(Non-key attribute)
  • 从上面的笛卡儿积中取出有实际意义的元组来构造关系
    • 关系: S A P ( S U P E R V I S O R , S P E C I A L I T Y , P O S T G R A D U A T E ) SAP(SUPERVISOR,SPECIALITY,POSTGRADUATE) SAP(SUPERVISORSPECIALITYPOSTGRADUATE)
    • 假设:导师与专业: 1 : 1 1:1 1:1, 导师与研究生: 1 : n 1:n 1:n
    • 主码:POSTGRADUATE(假设研究生不会重名)
    • SAP关系可以包含三个元组在这里插入图片描述
  • 关系可以有三种类型
    • 基本关系(通常又称为基本表基表): 实际存在的表,是实际存储数据的逻辑表示. 基本关系的性质:
      • (1) 列是同质的(Homogeneous), 即每一列中的分量来自同一个域
      • (2) 不同的列可出自同一个域, 其中的每一列称为一个属性, 不同的属性要给予不同的属性名
        • 例如,在上面的例子中,导师属性和研究生属性都从 PERSON 域 (张清玫,刘逸,李勇,刘晨、王敏) 中取值
          人(PERSON)
      • (3) 列的顺序无所谓,列的次序可以任意交换; 行的顺序无所谓,行的次序可以任意交换
      • (4) 任意两个元组的候选码不能相同
      • (5) 分量必须取原子值,即每一个分量都必须是不可分的数据项 (规范
        条件中最基本的一条)
    • 查询表: 查询结果对应的表
    • 视图表: 由基本表或其他视图表导出的表,是虚表,不对应实际存储的数据

关系模式

什么是关系模式(Relation Schema)

  • 关系模式; 关系
  • 关系模式是对关系的描述
    • 元组集合的结构, 属性构成, 属性来自的域, 属性与域之间的映象关系, 元组语义以及完整性约束条件, 属性间的数据依赖关系集合

定义关系模式

  • 关系模式可以形式化地表示为:
    R ( U , D , D O M , F ) R(U,D,DOM,F) RUDDOMF
R R R 关系名
U U U 组成该关系的属性名集合
D D D 属性组 U U U 中属性所来自的域
D O M DOM DOM 属性向域的映象集合,即说明属性分别出自哪个域
F F F 属性间的数据依赖关系集合
  • 例: 导师和研究生出自同一个域: “人”,取不同的属性名,并在模式中定义属性向域的映象:
    • D O M ( S U P E R V I S O R ) = D O M ( P O S T G R A D U A T E ) = P E R S O N DOM(SUPERVISOR)= DOM(POSTGRADUATE)= PERSON DOMSUPERVISOR=DOMPOSTGRADUATE=PERSON

  • 关系模式通常可以简记为:
    R ( U ) R(U) R(U) R ( A 1 , A 2 , … , A n ) R(A_1,A_2,…,A_n) R(A1A2An)
    • A 1 , A 2 , … , A n A_1,A_2,…,A_n A1A2An: 属性名
    • 域名及属性向域的映象常常直接说明为属性的类型、长度

关系模式与关系

  • 关系模式: 对关系的描述, 静态的、稳定的
  • 关系: 关系模式在某一时刻的状态或内容; 动态的、随时间不断变化的

关系模式和关系往往统称为关系,通过上下文加以区别

关系数据库

  • 关系数据库: 在一个给定的应用领域中,所有关系的集合构成一个关系数据库
    • 关系数据库的: 关系数据库模式, 是对关系数据库的描述
      • 关系数据库模式包括: 若干域的定义、在这些域上定义的若干关系模式
    • 关系数据库的: 关系模式在某一时刻对应的关系的集合,简称为关系数据库

关系操作

基本关系操作

  • 常用的关系操作 (增删改查)
    • 查询 (Query):选择、投影、连接、除、并、交、差、笛卡尔积… 其中,选择、投影、并、差、笛卡尔积 是 5 种基本操作, 其他操作是可以用基本操作来定义和导出的
      • 关系的查询表达能力很强,是关系操作中最主要的部分
    • 数据更新:插入、删除、修改
  • 关系操作的特点:
    • 集合操作方式:操作的对象和结果都是集合,一次一集合。相应地,非关系数据模型的数据操作方式则为一次一记录

关系数据库语言的分类

  • 关系语言是一种高度非过程化的语言,用户不必请求 DBA 为其建立特殊的存取路径,存取路径的选择由 RDBMS 的优化机制来完成
    • 例如,在一个存储有几百万条记录的关系中查找符合条件的某一个或某些记录,从原理上讲可以有多种查找方法。例如,可以顺序扫描这个关系,也可以通过某种索引来查找。不同的查找路径(或者称为存取路径)的效率是不同的,有的完成某一个查询可能很快,有的可能极慢;RDBMS 中研究和开发了查询优化方法,系统可以自动地选择较优的存取路径,提高查询效率

  • 关系代数语言 (ISBL): 用对关系的运算来表达查询要求
  • 关系演算语言:用谓词来表达查询要求
    • 元组关系演算语言 (ALPHA, QUEL)
      • 谓词变元的基本对象是元组变量
    • 域关系演算语言 (QBE)
      • 谓词变元的基本对象是变量
  • 具有关系代数和关系演算双重特点的语言 (SQL(Structured Query Language))

关系代数,元组关系演算和域关系演算三种语言在表达能力上是完全等价的

关系的完整性

  • 关系模型的完整性规则是对关系的某种约束条件

关系的三类完整性约束

  • 实体完整性参照完整性
    • 关系模型必须满足的完整性约束条件
    • 称为关系的两个不变性,应该由关系系统自动支持
  • 用户定义的完整性:应用领域需要遵循的约束条件,体现了具体领域中的语义约束

实体完整性 (Entity Integrity)

  • 实体完整性规则: 若属性 A A A 是基本关系 R R R 的主属性,则属性 A A A 不能取空值 (是 “不知道” 或 “不存在” 的值)
    • (1) 实体完整性规则是针对基本关系而言的。一个基本表通常对应现实世界的一个实体集
    • (2) 现实世界中的实体是可区分的,即它们具有某种唯一性标识
    • (3) 关系模型中以主码作为唯一性标识。主码中的属性即主属性不能取空值。主属性取空值,就说明存在某个不可标识的实体,即存在不可区分的实体,这与第(2)点相矛盾,因此这个规则称为实体完整性

参照完整性 (Referential Integrity)

关系间的引用

  • 在关系模型中实体及实体间的联系都是用关系来描述的,因此可能存在着关系与关系间的引用
    • 例: 学生实体、专业实体
      学 生 ( 学 号 , 姓 名 , 性 别 , 专 业 号 , 年 龄 ) 专 业 ( 专 业 号 , 专 业 名 ) 学生(学号,姓名,性别,专业号,年龄)\\ 专业(专业号,专业名)
      • 学生关系引用了专业关系的主码“专业号”
      • 学生关系中的“专业号”值必须是确实存在的专业的专业号,即专业关系中有该专业的记录
    • 例: 学生、课程、学生与课程之间的多对多联系
      学 生 ( 学 号 , 姓 名 , 性 别 , 专 业 号 , 年 龄 ) 课 程 ( 课 程 号 , 课 程 名 , 学 分 ) 选 修 ( 学 号 , 课 程 号 , 成 绩 ) 学生(学号,姓名,性别,专业号,年龄) \\课程(课程号,课程名,学分)\\ 选修(学号,课程号,成绩)
    • 例: 学生实体及其内部的一对多联系
      学 生 ( 学 号 , 姓 名 , 性 别 , 专 业 号 , 年 龄 , 班 长 ) 学生(学号,姓名,性别,专业号,年龄,班长) 在这里插入图片描述
      • “学号”是主码,“班长”是外码,它引用了本关系的“学号”: “班长” 必须是确实存在的学生的学号

外码 (Foreign Key)

  • F F F 是基本关系 R R R 的一个或一组属性,但不是关系 R R R 的码。如果 F F F 与基本关系 S S S 的主码 K K K 相对应,则称 F F F 是基本关系 R R R 的外码
    • 基本关系 R R R 称为参照关系(Referencing Relation)
    • 基本关系 S S S 称为被参照关系(Referenced Relation)或目标关系(Target Relation)

  • 关系 R R R S S S 不一定是不同的关系
  • 目标关系 S S S 的主码 K K K 和参照关系的外码 F F F 必须定义在同一个(或一组)域上
  • 外码并不一定要与相应的主码同名, 当外码与相应的主码属于不同关系时,往往取相同的名字,以便于识别

  • 例: 学生关系的“专业号"与专业关系的主码“专业号” 相对应
    • “专业号”属性是学生关系的外码
    • 专业关系是被参照关系,学生关系为参照关系
      在这里插入图片描述
  • 例: 选修关系的 “学号” 与学生关系的主码 “学号” 相对应, 选修关系的“课程号”与课程关系的主码“课程号”相对应
    • “学号”和“课程号”分别作为选修关系的外码, 它们一起作为选修关系的主码
    • 学生关系和课程关系均为被参照关系, 选修关系为参照关系
      在这里插入图片描述
  • 例: “班长”与本身的主码“学号”相对应
    • “班长”是外码
    • 学生关系既是参照关系也是被参照关系
      在这里插入图片描述

参照完整性规则

  • 参照完整性规则: 若属性(或属性组) F F F 是基本关系 R R R 的外码,它与基本关系 S S S 的主码 K K K 相对应,则对于 R R R 中每个元组在 F F F 上的值必须为:
    • 或者取空值 F F F 的每个属性值均为空值)
    • 或者等于 S S S某个元组的主码值

  • 例: 学生关系中每个元组的“专业号”属性只取两类值:
    • (1) 空值,表示尚未给该学生分配专业
    • (2) 非空值,这时该值必须是专业关系中某个元组的“专业号”值,表示该学生不可能分配一个不存在的专业
  • 例: 选 修 ( 学 号 , 课 程 号 , 成 绩 ) 选修(学号,课程号,成绩) , “学号”和“课程号”可能的取值:
    • (1) 选修关系中的主属性,不能取空值
    • (2) 只能取相应被参照关系中已经存在的主码值
  • 例: 学 生 ( 学 号 , 姓 名 , 性 别 , 专 业 号 , 年 龄 , 班 长 ) 学生(学号,姓名,性别,专业号,年龄,班长) ; “班长”属性值可以取两类值:
    • (1) 空值,表示该学生所在班级尚未选出班长
    • (2) 非空值,该值必须是本关系中某个元组的学号值

用户定义的完整性

  • 针对某一具体关系数据库的约束条件,反映某一具体应用所涉及的数据必须满足的语义要求
  • 关系模型应提供定义和检验这类完整性的机制,以便用统一的系统的方法处理它们,而不要由应用程序承担这一功能

  • 例: 课 程 ( 课 程 号 , 课 程 名 , 学 分 ) 课程(课程号,课程名,学分) ()
    • “课程号”属性必须取唯一值
    • 非主属性“课程名”也不能取空值
    • “学分”属性只能取值{1,2,3,4}

关系代数

概述

  • 关系代数运算的三个要素
    • 运算对象:关系
    • 运算结果:关系
    • 运算符:四类
      在这里插入图片描述在这里插入图片描述

传统的集合运算

  • (Union) R ∪ S R\cup S RS
    • R R R S S S 具有相同的目 n n n(即两个关系都有 n n n 个属性), 相应的属性取自同一个域
    • R ∪ S R\cup S RS 仍为 n n n 目关系,由属于 R R R 或属于 S S S 的元组组成
      R ∪ S = { t ∣ t ∈ R ∨ t ∈ S } R∪S = \{ t|t ∈ R∨t ∈S \} RS={ ttRtS}
  • (Difference) R − S R-S RS
    • R R R S S S 具有相同的目 n n n, 相应的属性取自同一个域
    • R − S R-S RS 仍为 n n n 目关系,由属于 R R R 而不属于 S S S 的所有元组组成
      R − S = { t ∣ t ∈ R ∧ t ∉ S } R- S = \{ t|t∈R∧t∉S \} RS={ ttRt/S}
  • (Intersection)
    • R R R S S S 具有相同的目 n n n, 相应的属性取自同一个域
    • R ∩ S R\cap S RS 仍为 n n n 目关系,由既属于 R R R 又属于 S S S 的元组组成
      R ∩ S = { t ∣ t ∈ R ∧ t ∈ S } = R − ( R − S ) R\cap S = \{ t|t∈R∧t∈S \}=R-(R-S) RS={ ttRtS}=R(RS)
  • 广义笛卡尔积
    • R R R n n n 目关系, k 1 k_1 k1 个元组
    • S S S m m m 目关系, k 2 k_2 k2 个元组
    • R × S R×S R×S
      • 列: ( n + m ) (n+m) (n+m) 列的元组的集合; 元组的前 n n n 列是关系 R R R 的一个元组, 后 m m m 列是关系 S S S 的一个元组
      • 行: k 1 × k 2 k_1×k_2 k1×k2 个元组
        R × S = { t r t s ⌢ ∣ t r ∈ R ∧ t s ∈ S } R×S = \{\overset{\frown}{t_r t_s} |t_r∈R ∧ t_s∈S \} R×S={ trtstrRtsS}
        在这里插入图片描述

专门的关系运算

引入几个记号

  • (1) R , t ∈ R , t [ A i ] R,t∈R,t[A_i] RtRt[Ai]
    设关系模式为 R ( A 1 , A 2 , … , A n ) R(A_1,A_2,…,A_n) R(A1A2An), 它的一个关系设为 R R R
    • t ∈ R t∈R tR 表示 t t t R R R 的一个元组
    • t [ A i ] t[A_i] t[Ai] 则表示元组 t t t 中相应于属性 A i A_i Ai 的一个
  • (2) A , t [ A ] , A ˉ A,t[A],Ā At[A]Aˉ
    • A = { A i 1 , A i 2 , … , A i k } A=\{A_{i1},A_{i2},…,A_{ik}\} A={ Ai1Ai2Aik},其中 A i 1 , A i 2 , … , A i k A_{i1},A_{i2},…,A_{ik} Ai1Ai2Aik A 1 , A 2 , … , A n A_1,A_2,…,A_n A1A2An 中的一部分,则 A A A 称为属性列或属性组
    • t [ A ] = ( t [ A i 1 ] , t [ A i 2 ] , … , t [ A i k ] ) t[A]=(t[A_{i1}],t[A_{i2}],…,t[A_{ik}]) t[A]=(t[Ai1]t[Ai2]t[Aik]) 表示元组 t t t 在属性列 A A A 上诸分量的集合
    • A ˉ Ā Aˉ 则表示 { A 1 , A 2 , … , A n } \{A_1,A_2,…,A_n\} { A1A2An} 中去掉 { A i 1 , A i 2 , … , A i k } \{A_{i1},A_{i2},…,A_{ik}\} { Ai1Ai2Aik} 后剩余的属性组
  • (3) t r t s ⌢ \overset{\frown}{t_r t_s} trts
    • R R R n n n 目关系, S S S m m m 目关系。 t r ∈ R t_r∈R trR t s ∈ S t_s∈S tsS t r t s ⌢ \overset{\frown}{t_r t_s} trts 称为元组的连接
    • t r t s ⌢ \overset{\frown}{t_r t_s} trts 是一个 n + m n+m n+m 列的元组,前 n n n 个分量为 R R R 中的一个 n n n 元组,后 m m m 个分量为 S S S 中的一个 m m m 元组
  • (4) 象集 Z x Z_x Zx(Images Set)
    • 给定一个关系 R ( X , Z ) R(X,Z) R(XZ) X X X Z Z Z 为属性组
      Z x = { t [ Z ] ∣ t ∈ R , t [ X ] = x } Z_x=\{t[Z]|t∈R,t[X]=x\} Zx={ t[Z]tRt[X]=x}
    • 它表示 R R R 中属性组 X X X 上值为 x x x 的诸元组在 Z Z Z 上分量的集合
    • Z x 1 = { Z 1 , Z 2 , Z 3 } Z_{x_1}=\{Z_1,Z_2,Z_3\} Zx1={ Z1Z2Z3}; Z x 2 = { Z 2 , Z 3 } Z_{x_2}=\{Z_2,Z_3\} Zx2={ Z2Z3}; Z x 3 = { Z 1 , Z 3 } Z_{x_3}=\{Z_1,Z_3\} Zx3={ Z1Z3}
      在这里插入图片描述

  • 以下举例使用 学生-课程数据库:
    • 学生关系 Student
      在这里插入图片描述
    • 课程关系 Course
      在这里插入图片描述
    • 选修关系 SC Student
      在这里插入图片描述
    • 多对多联系在这里插入图片描述

选择 (Selection)

  • 选择: 在关系 R R R 中选择满足给定条件的诸元组
    σ F ( R ) = { t ∣ t ∈ R ∧ F ( t ) = ‘ 真 ’ } σ_F(R)=\{t|t∈R∧F(t)=‘真’\} σF(R)={ ttRF(t)=}
    • F F F:选择条件,是一个逻辑表达式,基本形式为: X 1    θ    Y 1 X_1\ \ θ \ \ Y_1 X1  θ  Y1 ( θ \theta θ 为一个逻辑运算符)
  • 选择运算是从关系 R R R 中选取使逻辑表达式 F F F 为真的元组,是从行的角度进行的运算

  • : 查询信息系(IS 系)全体学生
    σ S d e p t = ‘ I S ’ ( S t u d e n t ) σ_{Sdept = ‘IS’} (Student) σSdept=IS(Student) σ 5 = ‘ I S ’ ( S t u d e n t ) σ_{5= ‘IS’} (Student) σ5=IS(Student) 结果:
    在这里插入图片描述

注意:因为 Sdept 属性的值是字符串,因此 ‘IS’ 一定要加单引号; Student 为要查询的关系名

投影 (Projection)

  • 投影: 从 R R R 中选择出若干属性列组成新的关系
    π A ( R ) = { t [ A ] ∣ t ∈ R } π_A(R) = \{ t[A] | t ∈R \} πA(R)={ t[A]tR}
    • A A A R R R 中的属性列
  • 投影操作主要是从的角度进行运算, 但投影之后不仅取消了原关系中的某些列,而且还可能取消某些元组(避免重复行)

  • : 查询学生的姓名和所在系, 即求 Student 关系上学生姓名和所在系两个属性上的投影 π S n a m e , S d e p t ( S t u d e n t ) π_{Sname,Sdept}(Student) πSnameSdept(Student) π 2 , 5 ( S t u d e n t ) π_{2,5}(Student) π25(Student)
    在这里插入图片描述
  • : 查询学生关系 Student 中都有哪些系
    π S d e p t ( S t u d e n t ) π_{Sdept}(Student) πSdept(Student)
    在这里插入图片描述

连接 (Join)

  • 连接: 从两个关系的笛卡尔积中选取属性间满足一定条件的元组
    R ⋈ A   θ   B S = { t r t s ⌢ ∣ t r ∈ R ∧ t s ∈ S ∧ t r [ A ]   θ   t s [ B ] } R\mathop{⋈}\limits_{A\ \theta\ B}S=\{\overset{\frown}{t_r t_s}| t_r ∈ R∧t_s ∈S∧t_r[A]\ θ\ t_s[B] \} RA θ BS={ trtstrRtsStr[A] θ ts[B]}
    • A A A B B B:分别为 R R R S S S度数相等且可比的属性组
    • θ θ θ:比较运算符
  • 连接运算从 R R R S S S 的广义笛卡尔积 R × S R×S R×S 中选取( R R R 关系)在 A A A 属性组上的值与( S S S 关系)在 B B B 属性组上值满足比较关系的元组

等值连接(equijoin)

  • 等值连接: θ θ θ 为 “=” 的连接运算; 即从关系 R R R S S S 的广义笛卡尔积中选取 A 、 B A、B AB 属性值相等的那些元组:
    R ⋈ A   =   B S = { t r t s ⌢ ∣ t r ∈ R ∧ t s ∈ S ∧ t r [ A ] = t s [ B ] } R\mathop{⋈}\limits_{A\ =\ B}S=\{\overset{\frown}{t_r t_s}| t_r ∈ R∧t_s ∈S∧t_r[A]=t_s[B] \} RA = BS={ trtstrRtsStr[A]=ts[B]}

自然连接(Natural join)

  • 自然连接是一种特殊的等值连接
    • 两个关系中进行比较的分量必须是相同的属性组
    • 在结果中把重复的属性列去掉
  • 自然连接的含义: R R R S S S 具有相同的属性组 B B B
    R ⋈ S = { t r t s ⌢ ∣ t r ∈ R ∧ t s ∈ S ∧ t r [ A ] = t s [ B ] } R\mathop{⋈}S=\{\overset{\frown}{t_r t_s}| t_r ∈ R∧t_s ∈S∧t_r[A]=t_s[B] \} RS={ trtstrRtsStr[A]=ts[B]}
    • 一般的连接操作是从行的角度进行运算, 自然连接还需要取消重复列,所以是同时从行和列的角度进行运算

  • : 关系 R R R 和关系 S S S 如图所示。一般连接, 等值连接, 自然连接的结果如下
    在这里插入图片描述

  • 例如之前提到的学生选课数据库,如果想得到每个学生选择的课程及其成绩,可以将 Student, SC, Coarse 作自然连接,但这里有个问题: 只有两个人选了课,其他人在做自然连接的时候就消失了。为了保留它们,就引出了外连接的概念
    在这里插入图片描述

外连接 (OUTERJOIN)

  • 外连接: 把舍弃的元组也保存在结果关系中,而在其他属性上填空值 (Null)
  • 左外连接 (LEFT OUTER JOIN / LEFT JOIN): 只把左边关系 R R R 中要舍弃的元组保留
  • 右外连接 (RIGHT OUTER JOIN或RIGHT JOIN): 只把右边关系 S S S 中要舍弃的元组保留

  • R R R S S S 见上例
    在这里插入图片描述

除 (Division)

  • 给定关系 R ( X , Y ) R(X,Y) R(XY) S ( Y , Z ) S(Y,Z) S(YZ),其中 X , Y , Z X,Y,Z XYZ 为属性组。 R R R 中的 Y Y Y S S S 中的 Y Y Y 可以有不同的属性名,但必须出自相同的域集
    • R R R S S S 的除运算得到一个新的关系 P ( X ) P(X) P(X) P P P R R R 中满足下列条件的元组在 X X X 属性列上的投影:
    • 元组在 X X X 上分量值 x x x 的象集 Y x Y_x Yx,包含 S S S Y Y Y 上投影的集合
      R ÷ S = { t r [ X ] ∣ t r ∈ R ∧ π Y ( S ) ⊆ Y x } R÷S = \{t_r[X]|t_r∈R∧π_Y(S)⊆Y_x\} R÷S={ tr[X]trRπY(S)Yx}

  • 设关系 R 、 S R、S RS 分别为下图的 ( a ) (a) (a) ( b ) (b) (b) R ÷ S R÷S R÷S 的结果为图 ( c ) (c) (c)
    在这里插入图片描述
  • 在关系 R R R 中, A A A 可以取四个值 { a 1 , a 2 , a 3 , a 4 } \{a_1,a_2, a_3,a_4\} { a1,a2,a3,a4}
    • a 1 a_1 a1 的象集为 { ( b 1 , c 2 ) , ( b 2 , c 3 ) , ( b 2 , c 1 ) } \{(b_1,c_2),(b_2,c_3),(b_2,c_1)\} { (b1,c2),(b2,c3),(b2,c1)}
    • a 2 a_2 a2 的象集为 { ( b 3 , c 7 ) , ( b 2 , c 3 ) } \{(b_3,c_7),(b_2,c_3)\} { (b3,c7),(b2,c3)}
    • a 3 a_3 a3 的象集为 { ( b 4 , c 6 ) } \{(b_4,c_6)\} { (b4,c6)}
    • a 4 a_4 a4 的象集为 { ( b 6 , c 6 ) } \{(b_6,c_6)\} { (b6,c6)}
  • S S S ( B , C ) (B,C) (B,C) 上的投影为
    { ( b 1 , c 2 ) , ( b 2 , c 1 ) , ( b 2 , c 3 ) } \{(b_1,c_2),(b_2,c_1),(b_2,c_3) \} { (b1,c2),(b2,c1),(b2,c3)}
  • 只有 a 1 a_1 a1 的象集包含了 S S S ( B , C ) (B,C) (B,C) 属性组上的投影, 所以 R ÷ S = { a 1 } R÷S =\{a_1\} R÷S={ a1}

  • 以学生-课程数据库为例,查询至少选修 1 号课程和 3 号课程的学生号码
    在这里插入图片描述
  • 首先建立一个临时关系 K K K
    在这里插入图片描述
  • 然后求: π S n o . C n o ( S C ) ÷ K = { 95001 } π_{Sno.Cno}(SC)÷K=\{95001\} πSno.Cno(SC)÷K={ 95001}

  • 查询至少选了学生 95001 所选全部课程的学生名单
    π S n a m e ( S t u d e n t ⋈ ( π S n o , C n o ( S C ) ÷ π C n o ( σ S n o = ′ 9500 1 ′ ( S C ) ) ) ) \pi_{Sname}\bigg(Student\mathop{⋈}\big(\pi_{Sno,Cno}(SC)÷\pi_{Cno}(\sigma_{Sno='95001'}(SC))\big)\bigg) πSname(Student(πSno,Cno(SC)÷πCno(σSno=95001(SC))))

  • 查询选修了 2 号课程的学生的学号
    π S n o ( σ C n o = ′ 2 ′ ( S C ) ) π_{Sno}(σ_{Cno='2'}(SC)) πSno(σCno=2(SC))

  • 查询至少选修了一门其直接先行课为 5 号课程的课程的学生姓名
    π S n a m e ( σ C p n o = ′ 5 ′ ( C o u r s e ⋈ S C ⋈ S t u d e n t ) ) \pi_{Sname}(\sigma_{Cpno='5'}(Course\mathop{⋈}SC\mathop{⋈}Student)) πSname(σCpno=5(CourseSCStudent)) π S n a m e ( σ C p n o = ′ 5 ′ ( C o u r s e ) ⋈ S C ⋈ π S n o , S n a m e ( S t u d e n t ) ) \pi_{Sname}(\sigma_{Cpno='5'}(Course)\mathop{⋈}SC\mathop{⋈}\pi_{Sno,Sname}(Student)) πSname(σCpno=5(Course)SCπSno,Sname(Student)) π S n a m e ( π S n o ( σ C p n o = ′ 5 ′ ( C o u r s e ) ⋈ S C ) ⋈ π S n o , S n a m e ( S t u d e n t ) ) \pi_{Sname}(\pi_{Sno}(\sigma_{Cpno='5'}(Course)\mathop{⋈}SC)\mathop{⋈}\pi_{Sno,Sname}(Student)) πSname(πSno(σCpno=5(Course)SC)πSno,Sname(Student))

注意,上面几种写法中,第一种虽然最简单,但也最低效


  • 查询选修了全部课程的学生号码和姓名
    π S n o , C n o ( S C ) ÷ π C n o ( C o u r s e ) ⋈ π S n o , S n a m e ( S t u d e n t ) \pi_{Sno,Cno}(SC)÷\pi_{Cno}(Course)\mathop{⋈}\pi_{Sno,Sname}(Student) πSno,Cno(SC)÷πCno(Course)πSno,Sname(Student)

还有一组老师出的题,答案存在 3.2 的相册里了。掌握上面例题的话还算简单,可能考试也是这种程度,复习时可以看看

关系演算

  • 关系演算以数理逻辑中的谓词演算为基础, 按谓词变元不同可分为
    • 元组关系演算:以元组变量作为谓词变元的基本对象
    • 域关系演算: 以域变量作为谓词变元的基本对象 (域关系演算语言 QBE)

元组关系演算语言 ALPHA

检索操作

在这里插入图片描述

  • 定额:规定检索的元组个数
  • 表达式 1指定语句的操作对象; 格式 ( “ ∣ | ” 代表 “或”):
    关 系 名 ∣ 关 系 名 . 属 性 名 ∣ 元 组 变 量 . 属 性 名 ∣ 集 函 数 [ , … ] 关系名|关系名.属性名|元组变量.属性名|集函数[,… ] ..[]
  • 操作条件:将操作结果限定在满足条件的元组中; 格式:逻辑表达式
  • 表达式2:指定排序方式; 格式:
    关 系 名 . 属 性 名 ∣ 元 组 变 量 . 属 性 名 [ , … ] 关系名.属性名|元组变量.属性名[,… ] ..[]

简单检索 (即不带条件的检索)

在这里插入图片描述


  • 查询所有被选修的课程号码
    G E T    W    ( S C . C n o ) GET\ \ W\ \ (SC.Cno) GET  W  (SC.Cno)

  • 查询所有学生的数据
    G E T    W    ( S t u d e n t ) GET\ \ W\ \ (Student) GET  W  (Student)

限定的检索 (即带条件的检索)

在这里插入图片描述


  • 查询信息系 (IS) 中年龄小于 20 岁的学生的学号和年龄
    G E T    W    ( S t u d e n t . S n o , S t u d e n t . S a g e ) :         S t u d e n t . S d e p t = ′ I S ′ ∧ S t u d e n t . S a g e < 20 GET\ \ W\ \ (Student.Sno,Student.Sage):\\ \ \ \ \ \ \ \ Student.Sdept='IS'∧Student.Sage<20 GET  W  (Student.SnoStudent.Sage):       Student.Sdept=ISStudent.Sage<20

带排序的检索

在这里插入图片描述


  • 查询计算机科学系(CS)学生的学号、年龄,结果按年龄降序排序
    G E T    W    ( S t u d e n t . S n o , S t u d e n t . S a g e ) :         S t u d e n t . S d e p t = ‘ C S ’    D O W N    S t u d e n t . S a g e GET\ \ W\ \ (Student.Sno,Student.Sage):\\ \ \ \ \ \ \ \ Student.Sdept=‘CS’\ \ DOWN \ \ Student.Sage GET  W  (Student.SnoStudent.Sage):       Student.Sdept=CS  DOWN  Student.Sage

带定额的检索

在这里插入图片描述


  • 取出一个信息系学生的学号。
    G E T    W    ( 1 )    ( S t u d e n t . S n o ) : S t u d e n t . S d e p t = ′ I S ′ GET \ \ W\ \ (1)\ \ (Student.Sno): Student.Sdept='IS' GET  W  (1)  (Student.Sno):Student.Sdept=IS

  • 查询信息系年龄最大的三个学生的学号及其年龄,结果按年龄降序排序
    G E T    W    ( 3 )    ( S t u d e n t . S n o , S t u d e n t . S a g e ) :         S t u d e n t . S d e p t = ′ I S ′    D O W N    S t u d e n t . S a g e GET\ \ W\ \ (3)\ \ (Student.Sno,Student.Sage): \\\ \ \ \ \ \ \ Student.Sdept='IS'\ \ DOWN \ \ Student.Sage GET  W  (3)  (Student.SnoStudent.Sage):       Student.Sdept=IS  DOWN  Student.Sage

用元组变量的检索

  • 元组变量: 表示可以在某一关系范围内变化(也称为范围变量 Range Variable)
    • 用途:
      • 简化关系名:设一个较短名字的元组变量来代替较长的关系名
      • 操作条件中使用量词时必须用元组变量
    • 定义元组变量
      • 格式:RANGE 关系名 变量名
      • 一个关系可以设多个元组变量

  • 查询信息系学生的名字
    R A N G E    S t u d e n t    X G E T    W    ( X . S n a m e ) : X . S d e p t = ‘ I S ’ RANGE\ \ Student\ \ X\\ GET\ \ W \ \ (X.Sname):X.Sdept=‘IS’ RANGE  Student  XGET  W  (X.Sname):X.Sdept=IS

用存在量词的检索

  • 查询选修2号课程的学生名字
    R A N G E    S C    X G E T    W    ( S t u d e n t . S n a m e ) : ∃ X ( X . S n o = S t u d e n t . S n o ∧ X . C n o = ′ 2 ′ ) RANGE\ \ SC\ \ X\\ GET\ \ W \ \ (Student.Sname):∃X(X.Sno=Student.Sno∧X.Cno='2') RANGE  SC  XGET  W  (Student.Sname):X(X.Sno=Student.SnoX.Cno=2)

  • 查询选修了这样课程的学生学号,其直接先行课是6号课程
    R A N G E    C o u r s e    C X G E T    W    ( S C . S n o ) : ∃ C X ( C X . C n o = S C . C n o ∧ C X . P c n o = ′ 6 ′ ) RANGE\ \ Course\ \ CX \\GET\ \ W\ \ (SC.Sno):∃CX (CX.Cno=SC.Cno∧CX.Pcno='6') RANGE  Course  CXGET  W  (SC.Sno):CX(CX.Cno=SC.CnoCX.Pcno=6)

  • 查询至少选修一门其先行课为6号课程的学生名字
    R A N G E    C o u r s e    C X               S C    S C X G E T    W    ( S t u d e n t . S n a m e ) : ∃ S C X ( S C X . S n o = S t u d e n t . S n o ∧ ∃ C X ( C X . C n o = S C X . C n o ∧ C X . P c n o = ′ 6 ′ ) ) RANGE\ \ Course\ \ CX\\ \ \ \ \ \ \ \ \ \ \ \ \ \ SC\ \ SCX\\ GET\ \ W \ \ (Student.Sname): ∃SCX (SCX.Sno=Student.Sno∧ ∃CX (CX.Cno=SCX.Cno∧CX.Pcno='6')) RANGE  Course  CX             SC  SCXGET  W  (Student.Sname):SCX(SCX.Sno=Student.SnoCX(CX.Cno=SCX.CnoCX.Pcno=6))前束范式形式:
    G E T    W    ( S t u d e n t . S n a m e ) : ∃ S C X ∃ C X ( S C X . S n o = S t u d e n t . S n o ∧ C X . C n o = S C X . C n o ∧ C X . P c n o = ′ 6 ′ ) GET \ \ W \ \ (Student.Sname):\\ ∃SCX∃CX(SCX.Sno=Student.Sno∧ CX.Cno=SCX.Cno∧CX.Pcno='6') GET  W  (Student.Sname):SCXCX(SCX.Sno=Student.SnoCX.Cno=SCX.CnoCX.Pcno=6)

带有多个关系的表达式的检索

  • 查询成绩为90分以上的学生名字与课程名字
    R A N G E    S C    S C X G E T    W    ( S t u d e n t . S n a m e , C o u r s e . C n a m e ) : ∃ S C X ( S C X . G r a d e ≥ 90 ∧ S C X . S n o = S t u d e n t . S n o ∧ C o u r s e . C n o = S C X . C n o ) RANGE\ \ SC\ \ SCX\\ GET\ \ W\ \ (Student.Sname,Course.Cname):\\ ∃SCX (SCX.Grade≥90∧ SCX.Sno=Student.Sno∧ Course.Cno=SCX.Cno) RANGE  SC  SCXGET  W  (Student.SnameCourse.Cname):SCX(SCX.Grade90SCX.Sno=Student.SnoCourse.Cno=SCX.Cno)

用全称量词的检索

  • 查询不选1号课程的学生名字
    R A N G E    S C    S C X G E T    W    ( S t u d e n t . S n a m e ) : ∀ S C X ( S C X . S n o ≠ S t u d e n t . S n o ∨ S C X . C n o ≠ ′ 1 ′ ) 或 者 写 成    ∀ S C X ( S C X . S n o = S t u d e n t . S n o → S C X . C n o ≠ ′ 1 ′ ) 或 者 写 成    ¬ ∃ S C X ( S C X . S n o = S t u d e n t . S n o ∧ S C X . C n o = ′ 1 ′ ) RANGE\ \ SC\ \ SCX\\ GET\ \ W\ \ (Student.Sname):\\ ∀SCX(SCX.Sno ≠Student.Sno ∨SCX.Cno≠ '1')\\或者写成\ \ ∀SCX(SCX.Sno= Student.Sno →SCX.Cno≠ '1')\\或者写成\ \ ¬∃SCX(SCX.Sno=Student.Sno ∧ SCX.Cno='1') RANGE  SC  SCXGET  W  (Student.Sname):SCX(SCX.Sno=Student.SnoSCX.Cno=1)  SCX(SCX.Sno=Student.SnoSCX.Cno=1)  ¬SCX(SCX.Sno=Student.SnoSCX.Cno=1)

用两种量词的检索

  • 查询选修了全部课程的学生姓名
    R A N G E    C o u r s e    C X             S C    S C X G E T    W    ( S t u d e n t . S n a m e ) : ∀ C X ∃ S C X ( S C X . S n o = S t u d e n t . S n o ∧ S C X . C n o = C X . C n o ) RANGE\ \ Course\ \ CX\\ \ \ \ \ \ \ \ \ \ \ \ SC\ \ SCX\\ GET\ \ W\ \ (Student.Sname):\\ ∀CX ∃SCX(SCX.Sno=Student.Sno∧SCX.Cno=CX.Cno) RANGE  Course  CX           SC  SCXGET  W  (Student.Sname):CXSCX(SCX.Sno=Student.SnoSCX.Cno=CX.Cno) W = { s . S n a m e ∣ s ∈ S t u d e n t ∧ ( ∀ c ) [ c ∈ C o u r s e → ( ∃ s c ) ( s c ∈ S C ∧ s c . S n o = s . S n o ∧ s c . C n o = c . C n o ) ] } W=\{ s.Sname| s\in Student∧(\forall c)[c\in Course\rightarrow (∃sc)(sc∈SC∧sc.Sno=s.Sno∧sc.Cno=c.Cno)]\} W={ s.SnamesStudent(c)[cCourse(sc)(scSCsc.Sno=s.Snosc.Cno=c.Cno)]}
    在这里插入图片描述

用蕴函(Implication)的检索

  • 查询最少选修了 95002 学生所选课程的学生学号
    在这里插入图片描述

集函数 (Aggregation function)

  • 常用集函数 / 内部函数(Build-in function)
    在这里插入图片描述

  • 查询学生所在系的数目
    G E T    W    ( C O U N T ( S t u d e n t . S d e p t ) ) GET\ \ W\ \ (COUNT(Student.Sdept)) GET  W  (COUNT(Student.Sdept))
    • COUNT 函数在计数时会自动排除重复值

  • 查询信息系学生的平均年龄
    G E T    W    ( A V G ( S t u d e n t . S a g e ) ) : S t u d e n t . S d e p t = ′ I S ′ GET\ \ W\ \ (AVG(Student.Sage)): Student.Sdept='IS' GET  W  (AVG(Student.Sage)):Student.Sdept=IS

更新语句

修改操作

  • (1) 用 HOLD 语句将要修改的元组从数据库中读到工作空间中 (HOLD 语句是带上并发控制的 GET 语句)
    H O L D    工 作 空 间 名    ( 表 达 式 1 )    [ : 操 作 条 件 ] HOLD\ \ 工作空间名\ \ (表达式1)\ \ [:操作条件 ] HOLD    (1)  []
  • (2) 用宿主语言修改工作空间中元组的属性
  • (3) 用 UPDATE 语句将修改后的元组送回数据库中
    U P D A T E    工 作 空 间 名 UPDATE\ \ 工作空间名 UPDATE  

  • 把 95007 学生从计算机科学系转到信息系
HOLD W (Student.Sno,Student.Sdetp): Student.Sno = '95007'
MOVE 'IS' TO W.Sdept		(用宿主语言进行修改)
UPDATE W

插入操作

  • (1) 用宿主语言在工作空间中建立新元组
  • (2) 用 PUT 语句把该元组存入指定关系中 (PUT 语句只对一个关系操作)
    P U T    工 作 空 间 名    ( 关 系 名 ) PUT\ \ 工作空间名\ \ (关系名) PUT    ()

  • 学校新开设了一门2学分的课程“计算机组织与结构”,其课程号为8,直接先行课为6号课程。插入该课程元组
MOVE '8' TO W.Cno
MOVE '计算机组织与结构' TO W.Cname 
MOVE '6' TO W.Cpno
MOVE '2' TO W.Ccredit
PUT W (Course)

删除操作

  • (1) 用 HOLD 语句把要删除的元组从数据库中读到工作空间中
  • (2) 用 DELETE 语句删除该元组
    D E L E T E    工 作 空 间 名 DELETE \ \ 工作空间名 DELETE  

  • 95110学生因故退学,删除该学生元组
HOLD W (Student):Student.Sno='95110' 
DELETE W

  • 将学号 95001 改为 95102
HOLD W (Student):Student.Sno='95001' 
DELETE W
MOVE '95102' TO W.Sno
MOVE '李勇' TO W.Sname
MOVE '男' TO W.Ssex
MOVE '20' TO W.Sage
MOVE 'CS' TO W.Sdept
PUT W (Student)

  • 删除全部学生
HOLD W (Student) 
DELETE W
  • 为保证参照完整性,删除 S t u d e n t Student Student 中元组时相应地要删除 S C SC SC 中的元组
HOLD W (SC)
DELETE W

域关系演算语言 QBE

QBE:Query By Example

  • 查询要求:以填写表格的方式构造查询, 用示例元素(域变量)来表示查询结果可能的情况
  • 查询结果:以表格形式显示
    在这里插入图片描述

检索操作

  • (1) 用户提出要求;
  • (2) 屏幕显示空白表格;
    在这里插入图片描述
  • (3) 用户在最左边一栏输入要查询的关系名,例如 Student;
    在这里插入图片描述
  • (4) 系统显示该关系的属性名
    在这里插入图片描述
  • (5) 用户在上面构造查询要求 (打印信息系全体学生的姓名)
    在这里插入图片描述
  • (6) 屏幕显示查询结果
    在这里插入图片描述

构造查询的几个要素

  • 示例元素: 即域变量,一定要加下划线
    • 示例元素是这个域中可能的一个值,它不必是查询结果中的元素
  • 打印操作符 P P P.: 指定查询结果所含属性列
  • 查询条件: 不用加下划线
    • 可使用比较运算符 > , ≥ , < , ≤ , = >,≥,<,≤,= ≠ ≠ =; 其中 = = 可以省略
  • 排序要求

  • 查询全体学生的全部数据
    在这里插入图片描述
  • 显示全部数据也可以简单地把 P. 操作符作用在关系名上
    在这里插入图片描述

  • 求年龄大于19岁的学生的学号
    在这里插入图片描述

  • 求计算机科学系年龄大于19岁的学生的学号
    • 方法(1):把两个条件写在同一行上
      在这里插入图片描述
    • 方法(2):把两个条件写在不同行上,但使用相同的示例元素值
      在这里插入图片描述

  • 查询既选修了1号课程又选修了2号课程的学生的学号
    在这里插入图片描述

  • 查询计算机科学系或者年龄大于19岁的学生的学号 (注意:这里的示例元素是不一样的,代表“或”的关系)
    在这里插入图片描述

  • 查询未选修1号课程的学生姓名
    在这里插入图片描述思路:显示学生名字,而该学生选修1号课程的情况为假

  • 查询有两个人以上选修的课程号
    在这里插入图片描述思路:查询这样的课程 1 _ _ \mathop{1}\limits_{\_\_} __1,它不仅被 95001 _ _ _ _ _ _ _ _ \mathop{95001}\limits_{\_\_\_\_\_\_\_\_} ________95001 选修, 而且也被另一个学生( ¬ 95001 _ _ _ _ _ _ _ _ ¬\mathop{95001}\limits_{\_\_\_\_\_\_\_\_} ¬________95001)选修了

多表连接

  • 查询选修1号课程的学生姓名
    在这里插入图片描述注意:示例元素 S n o Sno Sno 是连接属性,其值在两个表中要相同

集函数

  • 常用集函数:
    在这里插入图片描述

  • 查询信息系学生的平均年龄
    在这里插入图片描述

对查询结果排序

  • 升序排序:对查询结果按某个属性值的升序排序,只需在相应列中填入“AO.
  • 降序排序:按降序排序则填 “DO.
  • 多列排序:如果按多列排序,用“AO(i).”或“DO(i).”表示,其中 $$ 为排序的优先级, i i i 值越小,优先级越高

  • 查全体男生的姓名,要求查询结果按所在系升序
    在这里插入图片描述

修改操作

  • 把 95001 学生的年龄改为 18 岁
    • 方法 (1) :将操作符“U.”放在值上
      在这里插入图片描述
    • 方法 (2): 将操作符“U.”放在关系上
      在这里插入图片描述
      • 码 95001 标明要修改的元组。“U.”标明所在的行是修改后的新值。由于主码是不能修改的,所以系统不会混淆要修改的属性

  • 将计算机系所有学生的年龄都改为 18 岁
    在这里插入图片描述

这里可以不写示例元素吗?

  • 把 95001 学生的年龄增加 1 岁
    在这里插入图片描述

  • 将计算机系所有学生的年龄都增加1岁
    在这里插入图片描述

插入操作

  • 把信息系女生95701,姓名张三,年龄17岁存入数据库中
    在这里插入图片描述

删除操作

  • 删除学生 95089
    在这里插入图片描述
  • 为保证参照完整性,删除95089学生前,先删除95089学生选修的全部课程
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42437114/article/details/114593149
今日推荐