数据库复习
CH5 完整性
5.1 完整性约束
数据库完整性是指数据库中数据在逻辑上的一致性、正确性、有效性和相容性,那么完整性约束就是指用户插入、修改和删除操作时,DBMS为了保证数据库逻辑上的一致性、正确性、有效性和相容性所必需要检查的约束条件
C.J.Date在《An Introduction to Database Systems》一书中描述了四种完整性约束:
- 类型约束
- 属性约束
- 关系变量约束
- 数据库约束
C.J.Date提出的理论多半是建立在他自创的Tutorial D这个概念型数据库操作语言上的,下面的描述也是基于Tutorial D,注意不要和SQL混淆
(1)类型约束
类型约束是关于自定义类型的种类(或就是数域)以及值大小的约束,例如我们自定类型weight是实数类型且要求weight必须大于0,那么用Tutorial D定义一个类型约束如下:
TYPE weight POSSREP(RATIONAL) CONSTRAINT the_weight(weight) > 0.0;
POSSREP是possible representation(可能的表达方式),RATIONAL是有理数,the_加weight(前面提到过这是C.J.Date提出的对类型的操作符号,表示取值)weight作用于weight表示取值
(2)属性约束
属性约束就是定义关系时定义属性的类型产生的隐式约束,Tutorial D定义一个关系如下:
VAR S BASE RELATION(S# S#, status integer, city char);
比如上例的status就需要隐式遵守类型integer的类型约束
(3)关系变量约束
关系变量约束是对关系中元组的约束,如限定在伦敦的供应商状态一定是20:
Constraint sc1 is_empty(S where city = ‘London‘ and status ~= 20);
关系变量约束总是立即检查的
(4)数据库约束
关系变量约束是针对单个关系变量内部属性的约束(可以把关系变量就理解为关系/表),若约束涉及多个关系变量,则称之为数据库约束
如下的数据库约束,限定零件商的个数必须等于供应表中零件商的个数:
Constraint dbc1 count(SP(p#)) = count(P(p#));
(5)约束设计的黄金准则(Golden Rule)
关于Golden Rule,C.J.Date在这里想要表达的是我们定义的所有完整性约束都称之为内部约束,用户和DBMS都很清楚,比如体重必须大于0
然而我们设计的系统还有很多其他的外部约束,比如说我的体重是69.5,那么可以插入一个元组(jcguo, 69.5),但是插入(jcguo, 100.5)说我是个大胖子DBMS也是允许的,但这违反了现实世界的约束
这届涉及到之前在完整性定义中包括的正确性,C.J.Date认为这部分的完整性应该由用户或者说DBA在数据库外部限定,而不是交给DBMS
5.2 键Key
数据库中可以定义一种特殊的完整性约束——键(key,或者有时也翻译为码,我叫键比较顺口)
(1)各种键的定义
我们先来弄清楚各种键的定义:
- super key:超键,在关系中能够唯一标识元组的属性集
- candidate key:候选键(一般叫候选码),不含有多余属性/不可规约的超键
- primary key:主键,用户选做唯一表识元组的候选键,主键不能为null
- alternate key:可选键,候选键中除了主键以外,没有被用户选中的候选键
- foreign key:外键,是依赖于其他关系用于保证数据库逻辑完整性的约束
前四个key都很好理解,下面着重理解一下外键
(2)外键
关系R1声明在属性a1上创建外键FK,那么有:
- 这个外键FK必须reference(引用or以来)于另一个关系R2的候选键CK(一般reference另一个关系的主键,设CK创建于属性a2上)
- 这个reference的作用就是保证R1中属性a1中出现的值全部都在R2的a2中出现(完整性约束)
注意,外键FK并不要求R2中属性a2(CK)中出现的值都必须在R1中属性a1中出现,只是说单方面的约束:一旦在FK中出现则必须在CK中出现
外键还有另一个最重要的特性:referential action(关联动作),关联动作是一个隐式的特殊的触发器(每当满足出发条件时都会出发的动作集合)
下面在讲SQL完整性时会详细阐述SQL所支持的关联动作
5.3 SQL完整性
再次强调之前我们复习的所有完整性都是抽象性的概念,需要理解,那么这一小节SQL完整性则需要记忆了
(1)域约束
域约束(Domain Constraints)是SQL中对属性取值范围的约束,SQL声明域约束是在create table时完成的,支持带名称的显式约束声明以及不带名称的便捷约束声明两种方式:
create table Student(
S# char(10),
name char(20),
gender char(1),
constraint gc check (gender in (‘F‘, ‘M‘))
);
和下面等价:
create table Student(
S# char(10),
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘))
);
check从句中可以使用常用的比较关系操作符号,也可以使用in构造复合语句
给约束命名是为了某些特殊情况我们需要对约束进行修改,见我另外一篇博文:
另外SQL支持对已声明的table添加约束,使用alter table
语句,本课程暂不要求
(2)主键
SQL主键声明也是在create table时声明的,声明主键也有两种方式(显式和便捷式,显式主键不需要名字,因为一个关系/表只能有一个主键):
create table Student(
S# char(10),
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘)),
primary key (S#)
);
等价于:
create table Student(
S# char(10) primary key,
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘))
);
需要注意的是主键不能为空,不能插入一条不包含主键/主键为null的元组
primary key也可以作用于多个属性,如同前面概念讲解中所提到的,如何设计依需求而定
(3)unique
SQL中实现候选码使用unique约束,一个关系可以有多条unique约束
这句话翻译自老师ppt,个人觉得需要补充:
- SQL中淡化了candidate key的概念
- unique约束不仅可以实现候选码,也可以实现超码,也就是它没有不可约的限制
声明uniqle约束和主键类似,举例略
(4)外键
Foreign key之前留了个坑,这里直接举例吧:
create table Dept(
D# char(3) primary key,
name char(20),
type char(20) check (type in (‘engineering‘, ‘science‘, ‘business‘))
);
先创建了一个table Dept是系的关系,它的主键是D#,再创建一个Student表,它的属性D#上声明了一个引用Dept(D#)的外键:
create table Student(
S# char(10) primary key,
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘)),
D# char(3) foreign key references Dept(D#)
);
上面的声明等价于:
create table Student(
S# char(10) primary key,
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘)),
D# char(3),
contraint dfk foreign key references Dept(D#)
);
这里外键的约束就是限制学生表里的D#必须在系表里的D#中出现,那么很容易想到一个问题:如果USTC新任校长万老大把11系拆了,也就是系表里把D#为’011’的元组删除了的话,那我们11系的学生在Student表里的tuples不就不满足外键约束了吗
这时候就需要引入关联动作,关联动作分为级联(Cascade)动作和设置(Set)动作
1.级联动作
级联动作就是一系列的主键-外键-主键-外键-…约束引用链条的后端的某个主键delete或update,会产生(自动的,触发器)沿引用链条从后往前的一系列外键的delete或update操作
例如我们声明一个D#的删除级联,即:
create table Student(
S# char(10) primary key,
name char(20),
gender char(1) check (gender in (‘F‘, ‘M‘)),
D# char(3) foreign key references Dept(D#) on delete cascade
);
此时删除11系,那么根据级联动作定义,所有11系的学生都被删除,因为Student表通过外键引用了Dept表的主键
再假设Dept表引用了一个学校编号U#的外键,它是一个University表的主键,此时构成了一个引用链条,若这个University表删除了USTC的条目,那么根据级联动作定义USTC的11系,11系的学生都将被自动删除
级联动作也可以是on update cascade
,比如11系改系别号为1系了,那么11系的学生能够自动的把D#改成‘001’
DBMS处理级联动作是按事务来处理的,若在引用链条中某一个部分的级联动作违反了其他的完整性约束,那么整个修改动作都会被回滚(事务的概念),即修改失败
ppt中写道:
Referential integrity is only checked at the end of a transaction
–– Intermediate steps are allowed to violate referential integrity provided later steps remove the violation, otherwise it would be impossible to create some database states, e.g. insert two tuples whose foreign keys point to each other
很不幸的是在实验中我发现这条规则MySQL适用但Oracle并不适用(至少在PL/SQL中)
SQL支持一个外键同时有一个删除级联和一个更新级联
2.设置动作
设置动作比较好理解:
- on delete set null
- on delete set default
5.4 断言和触发器
(1)断言
SQL还支持使用断言(Assertion)在关系外部定义数据库必须满足的条件,语法为:
create assertion <assertion_name> check <predicate>;
Assertion有点类似于C.J.Date定义的数据库约束,可以针对单表或多表
ppt中一个复杂的例子,限定每个部门的工资总和必须小于d1部门的工资总和:
create assertion sac check(
not exists(
select * from EMP e2
where (select sum(sal) from EMP e1 where e1.d# = e2.d#)
>= (select sum(sal) from EMP where d# = ‘d1‘)
)
);
PS:我咋觉得第一个where后面要加d# ~= ‘d1‘ and
呢?
(2)触发器
触发器定义了数据库状态改变(元组修改)时需要自动执行的一系列动作,定义触发器包括两个要点:
- 触发条件
- 执行动作
触发器还有几个重要属性:
- 事前触发或事后触发
- 行触发还是整体触发
事前触发或事后触发比较好理解,行触发还是整体触发是指的是一条update或delete时若涉及多个元组的修改,是整体触发一次还是每个元组都触发一次
还是举例说明,下面是我在Oracle上创建的行触发器:
create or replace trigger countStud
after delete or insert or update on Stud
for each row
begin
update Dept set S_count = S_count+1 where D# = :new.D#;
update Dept set S_count = S_count-1 where D# = :old.D#;
end;
这是一个用来统计系里人数的触发器(或者说叫做保证系里人数完整性的触发器),分点说明:
after delete or insert or update on Stud
指定了触发器的条件和触发时机:当Stud表有删除、插入或更新操作之后触发for each row
声明这是一个行触发器,按行触发- begin和end包体里指定了触发器的动作
至于其中的:new
和是:old
保留变量,对应删除、插入或更新三个操作他们有如下含义:
含义 | insert | update | delete |
---|---|---|---|
:new | 新插入的元组 | 更新后的元组 | null |
:old | null | 更新前的元组 | 删除的元组 |