【数据库系统】课程笔记 - part 1
写在前面:
成绩
由于 db 的笔记有点太多了(
注:Lecture 1 为导论课,Lecture 7 为期中考试复习课,因此没有记录笔记。
另注:我已经尽可能写的详细了,但仍不保证知识点覆盖完全。
【数据库系统】课程笔记 - part 1:https://www.luogu.com.cn/article/47sozboa
【数据库系统】课程笔记 - part 2:https://www.luogu.com.cn/article/xcqqbg8c
【数据库系统】课程笔记 - part 3:https://www.luogu.com.cn/article/fk3j2hgg
Lecture 2 - Relational Model
relation 和 relationship 的区别
- 关系 (relation) 就是一张表 (table),由行 (row) 和列 (column) 组成,其对应着机器内部
- 联系 (relationship) 是各个实体 (entity) 之间的联系 (association),其对应着现实世界
Structure of Relational Databases
笛卡尔积
给出
而一个关系 (relation) 就是笛卡尔积的一个反映了特定意义的子集。
属性类型
之前提到了,一个关系是若干个域的笛卡尔积的子集,其中每个域都有一个名字,即为属性 (attribute),而域就是这个属性能取的所有值的集合。
由于关系理论第一范式 (1st NF),一般来说属性都要是原子的 (atomic),不可分割的 (indivisible),例如:
- 多值属性 (multivalued attribute) 不是原子的(例如手机号就是多值的,因为一个人可能有多个手机号)
- 复合属性 (composite attribute) 不是原子的(例如地址就是复合的,因为可以拆分成省、市、区、街道等更小的属性)
注意,空值 (null) 是每个域的元素,但它的引入可能会导致一些麻烦,这里暂不讨论。
关系的一些概念
- 关系模式 (relation schema) 描述了这个关系的结构,可以理解为表的设计图纸,是静态结构,例如:
Student-schema = (sid, name, sex, age, dept)- 它只定义了:表的名称、有哪些列/attribute、每一列的类型,约束等
- 关系实例 (relation instance) 则是指某一个时刻表中存放的所有数据,会动态发生改变
一些性质:
- 一个表中各个行/元组的顺序是无关的,可以任意调换
- 一个表中不允许出现两个完全相同的元组
关系模式 (relation schema)
用规范的语言来描述,则是:
- 设
A_1,A_2,\cdots,A_n 是n 个属性,则定义R=(A_1,A_2,\cdots,A_n) 为一个关系模式 - 并称
r(R) 为基于这个关系模式的一个关系(字面意思就是:一个符合R 结构的数据集合r )
关系实例 (relation instance)
每个关系实例中的一行都是一个元组,例如下图就是一个表:
数据库 (database)
一个数据库由若干个关系组成,通常来说我们会将一个复杂的关系拆分成若干个小的关系,具体如何判断是否需要拆分以及如何拆分会在之后的章节讨论,如果直接存储一个复杂的关系,那么可能会导致:
- 有非常多的冗余信息
- 可能需要空值,并产生一些问题
码/键 (key)
码为一个属性集合的子集,即
- 超码 (super key),指的是
K 的值足够唯一确定每个表中的元组 - 候选码 (candidate key),指的是大小最小的超码
- 主码 (primary key),指的是为候选码,同时被用户显式标识出来,通常主码会用下划线标识,主码涉及到实体完整性 (entity integrity)
- 外码 (foreign key),外码的定义涉及到参照完整性 (referential integrity),例如:
- 设有两张表
r(\underline{A},B),s(\underline{B},C) ,那么在r 表中B 就是一个外码,这个属性将r,s 两个表关联了起来,其中称r 为参照关系 (referencing relation),s 为被参照关系 (referenced relation),那么不难发现,r 中B 的值必须要么为空 (null),要么在s 中的B 中出现过
- 设有两张表
将所有的参照关系指向被参照关系,就可以得到表示关系模式的图,例如下面:
Fundamental Relational-Algebra Operations
六种基本运算,这六种运算都是以一个或两个关系为输入,并以一个关系为输出:
- 选择:
select - 投影:
project - 并:
union - 差:
set difference - 笛卡尔积:
Cartesian product - 重命名:
rename
选择运算
形式为
投影运算
形式为
并运算
形式为
差运算
形式为
笛卡尔积
形式为
重命名
形式为
Additional Relational-Algebra Operations
有四种运算:
- 交:
set intersection - 自然连接:
natural join - 除:
division - 赋值:
assignment
交运算
形式为
自然连接
形式为 这个符号没有对应的 latex 语法),表示:
-
r,s$ 的属性集合必须有交集,设为 $E - 取出
r,s 的E 中相同的元组并进行笛卡尔积,再将结果去重,新得到的属性集合为r,s 属性集合的并集
(哎不对是有语法的\bowtie)
有点难以描述,可以看下图:
除运算
形式为
-
r$ 的属性集必须是 $s$ 的属性集的超集,即 $S\subset R - 选出
R-S 中所有出现在全部s 中的元组
也有点难以描述,看下图:
赋值运算
形式为
Extended Relational-Algebra Operations
有两种运算:
- 广义投影:
generalized projection - 聚合函数:
aggregate functions
广义投影
在一般的投影运算中,只能指定原关系的一些属性,在广义投影中,可以指定这些属性之间的运算关系,例如:
聚合函数
一个聚合函数 (aggregate function) 以多个数为输入,并返回一个结果,典型的聚合函数有:
- 平均值:
avg - 总和:
sum - 计数:
count - 最大、最小值:
max/min
聚合函数的结构为
- 先以指定的属性
G_1,G_2,\cdots,G_n 进行分组,例如若n=1 ,则所有属性G_1 相同的元组都被分到一个组中 - 然后每个
F_i 为一个聚合函数,每一项A_i 为一个属性
注意:聚合函数计算出的结果是没有名称的,需要命名,例如:
下面是一个例子:
Modification of the Database
有三种应用场景:
- 删除:
deletion,一般可以用r\leftarrow r-E 来实现 - 插入:
insertion,一般可以用r\leftarrow r\cup E 来实现 - 更新:
update,一般可以用广义投影来实现(因为可以指定每个属性是如何运算得来的)
Lecture 3 - SQL
SQL (Structured Query Language,结构化查询语言),其包含三个部分:
- Data-Definition Language (DDL),具体包含如下几种操作:
- create/alter/drop table
- create/drop index/view/trigger
- Data-Manipulation Language (DML),具体包含如下几种操作:
- select
- insert/delete/update
- Data-Control Language (DCL),具体包含如下几种操作:
- grant/revoke
Data Definition Language
DDL 的主要功能包含以下几种:
- 定义每个关系的模式 (schema)
- 定义每个属性 (attribute) 的域 (domain),以及其完整性约束 (integrity constraints)
- 定义每个关系在物理层面的存储方式
- 定义关系上的视图
域的类型
char(n):长度固定为n 的字符串varchar(n):长度不超过n 的字符串int:整数,具体表示范围根据电脑而定smallint:与上类似,表示范围小一点numeric(p,d):固定小数点的实数,一共有p 位,小数点后d 位real/double precision:浮点数float(n):浮点数,精度至少为n 位date/time/timestamp:与日期,时间相关的内容
表的定义
一个标准的 SQL 语句为:
create table r(
A1 D1,
A2 D2,
...
An Dn,
(integrity constraint 1),
...
(integrity constraint k),
);
其中
然后是一些 integrity constraints 的类型:
not null:标识这个属性的值不能为 nullprimary key(A1,A2,...):标识这些属性为表的主键(注意,在比较高版本的 SQL 中,定义为主键自动保证了 not null)check(P):表示需要满足P 对应的限制
比如下面是一个例子:
create table branch(
branch_name varchar(30) not null,
branch_city varchar(30),
assets int,
primary key(branch_name),
check(assets>=0)
);
表的删除与修改
表的删除 (drop) 较为简单:
drop table r;
这样的语句即可删除名为
表的修改 (alter) 有以下几种类型:
alter table r [执行的操作];
- 添加字段:
add column - 删除字段:
drop column - 修改字段:
modify column - 重命名字段:
rename column
索引的建立和删除
索引 (index) 是一种用于加速查询,但减缓更新速度的手段(因为要同步更新索引)
索引的建立:
create (unique) index 索引名 on r(A1,A2,...);
其中 unique 可写可不写,如果加上就表示唯一索引
索引的删除:
drop index 索引名;
Basic Structure
select clause
一个标准的 select 语句的结构为:
select A1,A2,...
from r1,r2,...
where P;
其中
上面的这个语句等价于:
select语句允许结果中出现重复,如果需要去重,则应添加distinct:
select distinct age
from student
where age>=18;
与之对应的,如果不需要去重,也可以将 distinct 替换为 all(虽然默认就是允许重复的)。
select语句中,如果需要取出所有的属性,则可以以*代替,例如:
select * from student where age>=18;
select语句中允许对选中的属性进行数值操作,例如:
select price*1.2 from products;
select语句中允许对选中的属性重命名,例如:
select
product_name,
price,
discount,
price*(1-discount) as final_price
from products;
select语句中允许对from中的关系进行重命名,例如:
select
product_name,
price,
discount,
price*(1-discount) as final_price
from products P;
这个重命名通常会在多个关系做笛卡尔积的时候用到。
where clause
where 语句之后跟的判断条件为筛选依据,可以使用 and,or,not 等逻辑运算符进行不同条件的连接。
from clause
from 语句指定了筛选的范围,如果填写多个关系,则在这些关系的笛卡尔积中进行筛选。
请注意,如果两个进行笛卡尔积的关系中有相同的属性,则需要添加前缀来区分,例如:
select
a.id as 表A的id,
a.name as 表A的名称,
b.id as 表B的id,
b.name as 表B的名称
from 表A a, 表B b;
同时,from 语句中的一个表格可以重命名为不同的名称,使用两次,例如:
select distinct A.score
from student A, student B
where A.score>B.score and A.class=101 and B.class=102;
这样就选出了 101 班中比 102 班中某些学生成绩高的成绩了(某些和所有的区别会在之后写到)
order 语句
如果想要对筛选出的结果进行排序,那么可以在 where 之后添加一条 order by 指令,默认是升序的 (体现为 ASC),如果要改成降序则需要添加 DESC,例如:
select score from student order by score DESC;
这样就以降序排列了所有学生的分数。
另外,order by 语句中可以添加多项,此时的功能为多关键字排序。
select * from student
order by class ASC, score DESC;
这样就以班级编号为第一关键字升序排序,在班级相同时以分数为第二关键字降序排序了。
Set Operations
SQL 中集合操作一共有三种:交,并,差
union (all):将两个表格做并集,加上all表示结果可以重复intersect (all):将两个表格做交集,加上all表示结果可以重复except (all):将两个表格作差,加上all表示结果可以重复
Aggregate Functions
常见的聚合函数有:avg,min,max,sum,count,它们都作用于一列数据,并得到一个返回值。
例如一个最简单的情况:
select avg(balance) as avg_bal
from account
where branch_name='Perryridge';
那么如果想对每种 branch_name 都做平均值,就需要 group by 来指定分组方式了:
select avg(balance) as avg_bal
from accout
group by branch_name;
having clause
如果想对分组之后的各个组进行筛选,则需要加上 having 语句,其格式为:
select class, sum(score) as total_score
from scores
where score>=60 -- 分组前的筛选方法
group by class
having sum(score)>150; -- 分组后的筛选方法
注意 having 之后必须用聚合函数
Null Values
空值 (null) 是 SQL 中的一种特殊值,它代表着 “数据缺失” 或者 “数据不合法”,因此,与 null 相关的任何算术运算,逻辑运算得到的结果都是 null/unknown。同时,where 语句的结果如果是 unknown,那么它会被当做 false 处理。
所以,如果想判断某个内容是否为空,是不可以用 where A=null 的,而应当采用 where A is null/is not null。
同时,聚合函数(除了 count)在应用时都会忽略 null 值以免对结果产生影响。
Nested Subqueries
大致就是询问的嵌套,核心是 select 语句中涉及到的关系可以是子询问的结果。
- 引入
all/some,在判断的时候表示 "所有" 或 "某些"。
select branch_name
from branch
where assets > all(
select assets
from branch
where branch_city='Brooklyn'
);
select branch_name
from branch
where assets > (
select max(assets)
from branch
where branch_city='Brooklyn'
);
上面的这两句话是等价的。
- 引入
exists/not exists来测试select语句得到的结果是否为空
exists r -- r 非空
not exists r -- r 为空
- 引入
unique/not unique来测试select语句得到的结果中是否有重复的行
unique r -- r 中无重复
not unique r -- r 中有重复
Views
视图 (view) 建立的初衷就是限制一些列不向一些用户显示,建立视图的语句结构为:
create view 视图名称 as
select A1,A2,...
from r
where P
其本质为一张虚拟表,或者预保存的 select 语句。在创建完视图之后,可以把它当做正常的表格来使用。
Derived Relations
有些复杂的 SQL 语句可能需要保存某些查询得到的表进行重复使用,此时就可以使用 with 语句:
with 临时表名 as (
select 列 from 表 where 条件
)
select * from 临时表名;
也可以一次性创建多个临时表,中间用逗号分开,例如:
with
t1 as (select * from student where gender='男'),
t2 as (select * from scores where score>80)
select t1.name,t2.score
from t1 join t2 on t1.id=t2.stu_id;
Modification of the Database
Deletion
删除行,其结构为:
delete from 表
where 条件;
Insertion
添加新的行,其结构为:
insert into 表(A1,A2,...)
values (D1,D2,...),(...),...
Update
对原有的列进行修改,其结构为:
update 表
set [A1=a1,A2=a2,...] where 条件;
其中 where 这部分可以省略,例如:
update salary_record
set salary=salary*1.05
where dept_name='sale';
Case
这个语句是用来进行一个简单的 if 操作的:
case A1
when value1 then result1
when value2 then result2
else default_result
end -- 这个是针对一个具体的字段进行 if 判断
case
when condition1 then result1
when condition2 then result2
else default_result
end -- 这个是具体的条件判断
它会返回一个值,例如对薪资超过
update salary_record
set salary=case
when salary>10000 then salary*1.05
else salary*0.95
end
where dept_name='sale';
Joined Relations
分以下几种:
- Inner Join:取两边都有的部分
select *
from A
inner join B
on A.id=B.id
- Left Join:左侧表全部保留,右侧表匹配不到的就全补成 null
select *
from A
left join B
on A.id=B.id
- Right Join:右侧表全部保留,左侧表匹配不到的就全补成 null
select *
from A
right join B
on A.id=B.id
- Full Join:两张表全部保留,匹配不到的就全补成 null
select *
from A
full join B
on A.id=B.id
其中 left/right/full join 与第一种相反,称为 outer join,同时 join 默认是 inner join,因此 inner 可以省略。
Lecture 4 - Advanced SQL
这个部分介绍一些更加进阶的 SQL 语法以及它们能实现的功能。
SQL Data Types and Schemas
类似 C/C++ 中的 typedef,SQL 也有类似的用户自定义类型,有 create type 和 create domain 两种:
create type gender as enum('male','female','other'); -- create type 可以用于创建全新的数据类型
create domain positive_int as int check(value>0); -- create domain 是在原有类型的基础上添加限制
简而言之:
create type为创建全新的数据类型create domain为在原有数据类型上添加限制,可以避免重复写
Integrity Constraints
这里主要介绍四个内容:Domain Constraints, Referential Integrity, Assertions, Triggers。
完整性约束的作用就是在数据发生意外改变的时候维持数据内容的一致性,其包含实体完整性、参照完整性和用户定义的完整性约束。
最简单的完整性约束就是单个关系中的约束,例如关键字 not null/primary key/unique/check(P) 等等。
Domain Constraints
这个略去不提,就是上面 create domain 中添加的约束。
Referential Integrity
参照完整性 (Referential Integrity) 这部分就是之前提到的外键,回顾之前对外键的限制:参照关系中外键的值必须在被参照关系中实际存在,或为 null。
举一个最简单的例子来进行说明:
学生(学号(PK),姓名,专业号)
专业(专业号(PK),专业名称)
那么可以发现学生表中的 "专业号" 这一项参照了专业表中的 "专业号" 这一项,于是学生表中的 "专业号" 必须在专业表中出现过,否则会破坏参照完整性。
接下来用严谨的方式来进行定义:
-
设
r_1(R_1),r_2(R_2) 为两张表,它们的主键分别为K_1,K_2 -
称
R_2 的一个子集\alpha 为一个参照r_1 的外键 (foreign key),当且仅当:\prod_{\alpha}(r_2)\subseteq\prod_{K_1}(r_1)
于是在对
- 在学生表中插入一个新的元组时,需要检查其 "专业号" 这一项在专业表中是否出现过,若没有则抛出一个错误并拒绝插入
- 在专业表中删除一个元组时,需要检查学生表中有没有元组参照它,若没有则可以删除,若有,则要么抛出一个错误并拒绝删除,要么将学生表中参照它的元组也全部删除(这个根据用户的需求设置)
在 SQL 中,可以通过添加关键字来手动指定主键、外键等:
primary key:指定成为主键unique key:指定成为候选键(因为保证元素互不相同)foreign key:指定成为外键,需要指定参照的关系,在默认情况下,参照的是被参照关系的主键
例如:
foreign key (account-number) references account -- 自动参照 account 表中的 account-number
foreign key (account-number) references account(account-number) -- 手动指定也可以
Cascading Actions in SQL
之前提到了,删除被参照关系中的元组时,是否需要删除所有参照关系中参照它的内容,这部分就是级联行为 (cascading action),注意,级联行为是可以传递多次的。
指定级联行为的 SQL 语句为:
foreign key (branch-name)
references branch(branch-name)
[on delete cascade]
[on update cascade]
- 如果写了
on delete cascade,那么在 branch 表被删除的时候,会级联删除这张表中参照它的内容 - 如果写了
on update cascade,那么在 branch 表被修改的时候,会级联修改这张表中参照它的内容
(可见 lab3 的实验报告,里面有这部分的实验结果)
注意,null 值外键往往在级联操作时会引起很复杂的问题,因此请尽量避免这种情况。
Assertions
与 C/C++ 类似,断言 (assertion) 起到了一个保证条件成立的功能:
create assertion 断言名 check(P);
这样,系统会在每一次可能导致断言不成立的修改之后检查(因此引入断言可能会导致系统整体效率显著下降)。
另外,SQL 不支持 for all X, P(X) 这样的检查方式,因此只能用它的逆否命题
not exists X, such that not P(X)
Triggers
触发器 (trigger) 会在数据发生一些变化时自动进行一些行为,因此如果需要建立触发器,就必须指定:
- 在什么条件下触发器会执行
- 执行的内容是什么
触发器的具体语法比较复杂,大概有这么几个部分:
- 触发时机:
before/after - 触发事件:
insert/delete/update - 绑定对象:指定的一张表
- 行级对象:
new/oldnew指的是insert的新元组,或者update之后的新元组old指的是delete的元组,或者update之前的元组
这是豆包给出的一个例子:
CREATE TABLE user_log(
id INT PRIMARY KEY AUTO_INCREMENT,
uid INT,
old_name VARCHAR(20),
new_name VARCHAR(20),
update_time DATETIME DEFAULT NOW()
);
DELIMITER $$
CREATE TRIGGER trg_user_update_log
AFTER UPDATE ON user
FOR EACH ROW
BEGIN
INSERT INTO user_log(uid,old_name,new_name)
VALUES(OLD.id, OLD.name, NEW.name);
END$$
DELIMITER ;
Authorization
这一部分主要是关于 SQL 安全性,其实主要是两类语句:
grant <privilege list> on <table|view> to <user-list> -- 将特性的权限授予给某个用户
revoke <privilege list> on <table|view> from <user-list> [restrict|cascade]
-- 将特定的权限从某个用户处收回,加了 cascade 是因为可能有递归授予权限,此时 revoke 会全部收回
这里能授予和回收的权限有:insert/update/delete/references/select/all 等等。
Embedded SQL
Dynamic SQL
TBD(就是不会考,看不懂也懒得写,后面 to be done 的原因大致也是如此)
Lecture 5 - Entity-Relationship Model
Entity Sets
核心思想为:现实世界可以被抽象为若干实体 (entity) 以及实体之间的联系 (relationship)。
实体拥有多种属性(例如 "学生" 实体就包含姓名,性别,年龄等等),同时可以是具体的,也可以是抽象的。
一个实体集 (entity set) 是一系列同一类的,具有同种特性的实体的集合。
Attributes
之前对属性 (attribute) 已经有过介绍了,这里添加一些描述:
- 属性对实体进行特性的描述
- 属性也可以分成许多种类:
- 简单属性/复合属性 (simple/composite attribute):如 sex(简单)name(复合)等
- 单值属性/多值属性 (single-valued/multi-valued attribute):如电话号码(多值)
- 基属性/派生属性 (base/derived attribute):例如给出出生日期之后,年龄就是派生属性
Relationship Sets
联系 (relationship) 是两个或两个以上不同实体之间的关联。
联系集 (relationship set) 包含多个同类的联系,表示两个或两个以上实体集之间的关联。
上面的图就是一个例子,students 和 courses 是两个实体集,而 enrolled 就是这两个实体集之间的联系集。
(单条的 student 和 course 为单个实体,enroll 同理)
与实体类似,联系也可以拥有属性,来描述这个联系的特性。
Mapping Cardinality
指的是在一个联系集中,一个实体可以与另一类实体相联系的实体数目,这里数目可以是最多一个也可以是允许多个。
根据最多一个和允许多个可以分成这几类:
- one to one (1:1):例如当选总统(在个人和国家之间的联系,只能涉及一个人和一个国家)
- one to many (1:n):例如分班情况(在学生和班级之间的联系,可以涉及一个班级但只能涉及多个学生)
- many to one (n:1):例如就医情况(在病人和医生之间的联系,可以涉及多个病人但只能涉及一个医生)
- many to many (m:n):例如选课情况(在学生和课程之间的联系,每个学生可以选多门课,每门课也可以被多个学生选)
(允许有一些实体不参与到联系中)
Keys
Keys for Entity Sets
超码:能够唯一确定每个实体的一个或多个属性
候选码:大小最小的超码
主码:被用户选中的一个候选码
(这三种在之前都已经介绍过了)
Keys for Relationship Sets
一个联系集的超码为:参与联系的各个实体集的主码的并集(注意这里说的是超码)
因此考虑联系集的候选码时必须要关注这个联系集的 Mapping Cardinality,确定是多少对多少的。
存在多个候选码时,选择主码的时候需要考虑这个联系本身的语义。
E-R Diagram
使用图的方式对实体和联系进行表示:
- 用矩形框代表实体集
- 用菱形框代表联系集
- 用直线连接属性和实体集,或实体集和联系集(另外,也可以是描述性的属性和联系集之间)
- 用椭圆形框代表属性,其中
- 双线椭圆代表多值属性
- 虚线椭圆代表派生属性
- 有下划线的代表主键
Cardinality Constraints
E-R 图中的直线可以带上箭头,表示参与联系的实体数目,其中:
- 带箭头的直线表示唯一一个
- 不带箭头的直线表示多个
一个例子
Participation of an Entity Set in a Relationship Set
区分实体集中的所有实体是否都会参与到联系集中:
- 如果所有实体都会参与,则称为全参与 (total participation),在 E-R 图中用双实线表示
- 反之,如果不是所有实体都参与,则称为部分参与 (partial participation) 不额外做标记
Converting Non-Binary Relationships to Binary Form
有些时候一些复杂的联系可能会涉及到两个以上的实体集,这种情况较为复杂,因此可以考虑通过人为添加实体集的方法将所有联系集都变成二元的,例如下图所示:
Weak Entity Sets
弱实体集 (weak entity set) 与强实体集相区分,它们没有自己的主键,无法靠自己唯一标识实体,必须依赖另一个强实体集才能存在。
部分键/区分符 (partial key/discriminator) 是弱实体集的一组属性,用于在同一个强实体集下,区分不同的弱实体,因此需要强实体的主键,加上区分符,合起来才是弱实体的完整主键。
较为抽象,这里举一个例子:员工和员工家属
- 员工是强实体,因为可以通过 ID 来全局区分
- 员工家属是弱实体,没有 "家属编号" 作为主键,而是需要依赖 "员工" 这一强实体,才能以 "员工 ID + 家属关系/姓名" 这样作为员工家属的主键,于是也可以得到员工家属的 partial key 就是 "家属关系" 或 "家属姓名"
体现在 E-R 图中:
- 弱实体用双线矩形来标识
- 弱实体的 partial key 以虚下划线来标识
- 弱实体和其依赖的强实体之间的联系(这个也称为标识联系 (identifying relationship))用双线菱形来标识
Extended E-R Features
TBD