完整性约束
1. 完整性约束的概念
数据库的完整性是确保数据库中数据的一致性和正确性。为维护数据库的完整性,DBMS必须:
◎ 提供定义完整性约束条件的机制
◎ 提供完整性检查的方法
◎ 违约处理
2. 完整性约束的分类
○ 实体完整性
实体完整性是保证关系中的每一个元组都是可识别的和唯一的。
题外话:其实从上面的这个定义,大家可以想到主键不就是用来唯一地标识关系中的每一个元组吗?正是如此!所以实体完整性其实就是指的主键。
实体完整性约束规则:关系中主码的值不能为空,且不能重复。
实体完整性的约束机制:primary key。
○ 参照完整性
参照完整性规则:如果关系R2的外码X与关系R1的主码相对应,则外码X的每个值必须在关系R1主码的值中找到,或者为空值。
参照完整性的约束机制:foreign key、check、存储过程、触发器。
○ 用户自定义完整性(域完整性)
用户自定义完整性就是用户根据应用需求制定的数据约束条件。这种约束通常是针对某一具体属性的,比如设定某属性的默认值,要求某属性必须唯一,或者某属性的取值只能在某个范围以内等等,因此用户自定义完整性又常常被称作域完整性。
用户自定义完整性约束机制:unique、identity、default、check、not null、规则、存储过程、触发器。
实体完整性
1. 实体完整性的概念
primary key约束标识列或列集,这些列或列集的值唯一标识表中的行。
(1) 一个表只能有一个primary key约束;
(2)作为表定义的一部分在创建表时创建;
(3)添加到还没有primary key约束的表中;
(4)可对已有的primary key约束进行修改和删除;
(5)在一个表中不能有两行包含相同的主键值;主键也不能为null。
2. 实体完整性的定义
如果主键仅由一个属性组成,则可采用primary key列级约束:
【例1】
create Student
(
Sno char(9) primary key,
/*Sno为主键,必须非空且唯一*/
Sname char(20) not null,
Ssex char(2) ,
Sage smallint,
Sdept char(20)
);
此处的primary key仅仅作用在Sno列上,则被称为列级约束。
如果主码由一个或多个属性组成,则应该采用primary key表级约束:
【例2】
create table Student
(
Sno char(9),
Sname char(20) not null,
Ssex char(2) ,
Sage smallint,
Sdept char(20),
primary key (Sno)
/* 一个属性的情况也可采用表级primary key约束 */
);
【例3】
create table SC
(
Sno char(9) ,
Cno char(4) ,
Grade smallint,
primary key(Sno,Cno)
/*复合主键只能在表级定义*/
);
3. 实体完整性检查和违约处理
用Primary Key定义关系的主码后,每当用户程序对基本表插入一条记录或对主码进行更新操作时,DBMS将根据实体完整性规则进行自动检查:
○ 检查主码值是否唯一,如果不唯一就拒绝插入或修改。
○ 检查主码的各个属性是否为空,只要有一个为空就拒绝插入或修改。
从而保证实体完整性。
参照完整性
1. 参照完整性的概念
若属性组A是基本关系R的外码,它与基本关系S的主码K相对应,则R中每个元组在A上的值必须:要么取空值,要么等于S中某元组的主码值。

2. 参照完整性的定义
语法: foreign key (参照字段) references 被参照表(被参照字段)
◎ 在create table中用foreign key短语定义哪些列为外码
◎ 用references短语指明这些外码参照哪些表的主码
【例1】关系SC中(Sno,Cno)是主码。Sno,Cno分别参照引用S表的主码和C表的主码 ,试定义SC中的参照完整性。
create table SC (
Sno char(9) ,
Cno char(4) ,
Grade smallint,
primary key (Sno, Cno), /*在表级定义实体完整性*/
foreign key (Sno) references S (Sno),
/*在表级定义参照完整性*/
foreign key (Cno) references C (Cno)
/*在表级定义参照完整性*/
);
提防出错:在foreign key约束中参照表不必写出,否则会引发错误。即上例中的外键约束不能写成:
foreign key SC(Sno) references S (Sno) —— 错误的写法
3. 参照完整性检查和违约处理
(1)在参照关系中插入元组时的问题
Student(Sno,Sname,Sage, …) SC(Sno,Cno,Grade)
95001, 张三, 20, … 95001, DB, 90
95002, 李四, 19, … 95001, OS, 85
95002, DB, 86
思考:如果我们此时向SC表插入数据(95007,OS,90),那么会出现什么情况呢?
SC表中的Sno参考了Student中的Sno,而插入的95007在Student表中并不存在!这违反了参照完整性的约束规则,因此数据库采取的处理方式是:限制插入,即拒绝插入。这也是缺省的处理方式。
(2)在被参照关系中删除元组时的问题
被参照关系: 参照关系:
Student(Sno,Sname,Sage, …) SC(Sno,Cno,Grade)
95001, 张三, 20, … 95001, DB, 90
95002, 李四, 19, … 95001, OS, 82
95002, DB, 86
思考:如果现在从Student表中删除元组(95001,张三,20,...),那会怎样呢?
首先需要明确的是Student中的Sno被SC表中的Sno参考,因此元组(95001,张三,20,...)的删除会导致SC表中的元组(95001, DB, 90),(95001, OS, 82 )中的Sno(95001)在Student表中找不到参考的对象,同样这也会违背参照完整性的约束规则。此时数据库提供了两种机制供我们选择来处理这样的情况。这两种机制分别是级联删除和受限删除。
◎级联删除 cascade
级联删除的含义就是当被参照关系中的数据被删除时,同时从参照关系中删除那些参照被删除数据的其它所有数据。即当被参照关系Student中的元组(95001,张三,20,...)被删除时,因为参照关系SC中存在数据(95001, DB, 90),(95001, OS, 82 )参照了这条被删除的元组,因此将这两条元组从参照关系SC中一并删除。
◎受限删除:系统拒绝执行删除操作(缺省) not action
受限删除的含义就是当被参照关系中的数据被删除时,由于参照关系中的外码在被参照关系中将找不到参照的对象,因此数据库会拒绝这样的删除操作,即拒绝删除被参照关系中的元组(95001,张三,20,...)。只有当参照关系中没有任何元组的外码值等于被参照关系中这个即将被删除的元组的主码值时,这个元组才能被删除。
那么如何定义级联删除或受限删除呢?下面举例来说明定义方式:
【例2】
create table SC
( Sno int,
Cno int,
Grade smallint,
primary key(Sno,Cno),
foreign key (Sno) references Student (Sno)
on delete cascade
/*当删除Student表中的元组时级联删除SC表中相应的元组*/
foreign key (Cno) references Course (Cno)
on delete no action
/*当删除Course表中的元组造成了与SC表不一致时拒绝删除*/
);
(3)在参照关系中修改外码时的问题
Student(Sno,Sname,Sage, …) SC(Sno,Cno,Grade)
95001, 张三, 20, … 95001, DB, 90
95002, 李四, 19, … 95001, OS, 85
95002, DB, 86
思考:如果此时要将SC关系中元组( 95002, DB, 86)的外码值“95002”修改为“95006”,会出现什么样的情况?
按照目前所给出的数据来考虑,如果将SC关系中元组( 95002, DB, 86)的外码值“95002”修改为“95006”,那么将会导致95006在被参照关系Student中找不到可以被参照的数据,因为Student关系中的Sno不存在“95006”的情况,所以这就违背了参照完整性的约束规则。因此数据库会采用缺省的“限制修改”模式,拒绝这种情况下对参照关系中外码值的修改。
需要额外说明的是:如果Student关系中本来就存在某个元组的Sno为“95006”,那么本次的修改就变得合法。因为修改后的外码值在被参照关系的被参照列中可以找到。
(4)在被参照关系中修改主码时的问题
Student(Sno, Sname, Sage, …) SC(Sno, Cno, Grade)
95001, 张三, 20, … 95001, DB, 90
95002, 李四, 19, … 95001, OS, 85
95002, DB, 86
思考:如果此时我们要将被参照关系Student中的元组(95002, 李四, 19, … )的主码值修改为“95007”,那么又会如何?
其实这里的问题和第3种情况比较类似。如果将Student中的元组(95002, 李四, 19, … )的主码值修改为“95007”,那么原本参照此数据的参照关系中的元组( 95002, DB, 86)的外码值“95002”就找不到参照数据了!这还是违背了参照完整性的约束规则。因此对于这样的情况,数据库提供了两种处理方式供我们选择:受限修改和级联修改。
◎ 受限修改:拒绝此修改操作。只当参照关系中没有任何元组的外码值等于被参照关系中某个元组的主码值时,这个元组的主码值才能被修改。
◎ 级联修改:修改被参照关系中主码值的同时,用相同方法修改参照关系中对应的外码值。
那么如何定义级联修改或受限修改呢?下面举例来说明定义方式:
【例3】
create table SC
( Sno int,
Cno int,
Grade smallint,
primary key(Sno,Cno),
foreign key (Sno) references Student (Sno)
on delete cascade
/*当删除Student表中的元组时级联删除SC表中相应的元组*/
on update cascade,
/*当更新Student表中的元组时,级联更新SC表中相应的元组*/
foreign key (Cno) references Course (Cno)
on delete no action
/*当删除Course表中的元组造成了与SC表不一致时拒绝删除*/
on update cascade
/*当更新Course表中的元组时,级联更新SC表中相应的元组*/
);
闲言碎语:为什么级联的处理方式只出现在被参照关系中的被参照列被修改或删除的时候呢?为什么在参照关系中相应的外码数据被插入或修改的时候,只有受限而没有级联的选项呢?
这是因为参照数据是被动地来自于被参照数据,因此参照数据的修改和新增不应该影响到被参照数据,而被参照数据的修改或删除却可以影响到参照数据。这就类似于你的学习时间的安排参照了学校的课表,你适应课表,如果课表变化了,那么你也只能调整你自己的学习时间;不可能课表随着你的学习时间的变化而调整。因为参照这个课表的学生可不止你一人!
用户自定义完整性/域完整性
unique约束
在create table 中的属性定义后面加上unique关键字。unique关键字代表该列的每个值都必须唯一。
【例1】建立一个部门表DEPT,要求部门名称Dname取值唯一,Deptno为主码。
create table DEPT(
Deptno char(5) ,
Dname char(20) unique ,
…
primary key (Deptno)
);
identity
identity是用于定义种子增量列。主键是一个关系中必不可少的一部分,它唯一地标识了关系中的每一个元组。因此我们总会选择一些很特别的字段来充当主键的角色,比如学号、身份证号等等。这些字段作为主键是很理想的。我们在选择主键的时候,最理想的状态就是主键永不重复,且极少被修改。但在很多情况下,却未必会存在如此明确、永不重复、极少被修改的字段,可能你当时认为不会重复、不会被修改的字段在今后因为需求的变化而导致频繁地被修改,甚至出现不得不重复的现象。这就很尴尬了!所以一种通用的实践经验是在选择主键时,尽量不选择有实际意义的列来充当主键,而是新建一个毫无实际意义的列来担负起主键的重任。比如我们可以在一张表中新建一个id列,第一行的id值是1,第二行的id值是2,... ,以此类推,第n行的id值是n,每一行的id值均是在前一行的基础上做了一个累加1的操作,也就是一个首项为1、公差为1的等差数列。你当然可以随心所欲,改变首项和公差,比如(3,6,9,12,...),或(10,20,30,40,...)。只要符合你的需求,这都没什么问题。但不管怎样,这个id列的值都表现为一个等差数列。你在插入该列数据的时候,当然可以自己计算;但由于它是如此地有规律,所以数据库可以替你分担这个id值的计算工作,帮助你自动产生并插入这一列的值。这,就是种子增量的含义!
identity(标识种子,标识增量)
标识种子就相当于等差数列的首项,标识增量就相当于等差数列的公差。
【例2】建立学生表,其学号从9000开始递增,每次增1.
create table Student(
Sno int not null primary key identity(9000,1),
Sname varchar(20),
……
);
这个例子中,标识种子是9000,标识增量是1,所以第1行数据的Sno值是9000,第2行数据的Sno值是9001,...,以此类推。
default约束
使用default子句指定列值的默认值。如果你插入数据时,为该列指定了具体指,则默认值被覆盖;否则该列就会取默认值。
【例3】create table Student
(
Sno int unique primary key,
Sname varchar(20) default ’无名氏’,
joindate datetime default(getdate())
)
小知识:getdate():获取系统时间的SQL内置函数。
not null约束
在create table 中的属性定义后面加上not null关键字。默认情况下(即不加not null)为允许空。
【例4】 建立一个“学生选课”表SC,Sno, Cno为主码, Sno,
Cno和Grade不能为空。
create table SC(
Sno char(5),
Cno char(3),
Grade int not null ,
primary key (Sno, Cno));
/* 如果在表级定义实体完整性,隐含了Sno,Cno不允许取空值,则在列级不允许取空值的定义就 不必写了 * /
check约束
用check短语指定列值应满足的条件。
【例5】
create table SC
(
/* 插入sno列的数据时,检查sno的长度是否小于10;如果不小于10,则拒绝插入 * /
sno int check(len(sno)=10),
………….
/* 插入score列的数据时,检查score的值是否在0-100的区间内;如果不在此范围内,拒绝插入 * /
score int not null check(score>=0 and score<=100)
)
小知识:len(str):求取字符串长度的SQL内置函数。
【例6】建立一个职工表Person,要求Salary等于工资各项之和。
create table Person(
Pno char(5) ,
Pname char(20) unique ,
Salary1 int,
Salary2 int,
…
Salaryn int,
Salary int,
primary key (Pno),
/* 插入数据时,检查Salary列的值是否等于Salary1到Salaryn各列的值之和;如果不等,拒绝插入 * /
check(Salary= Salary1 + Salary2+…+ Salaryn));
【例7】创建表students,其中包括学号(sno),姓名(sname),性别(ssex),年龄(sage)、入学时间(sdate)5个属性。其中学号为主键、姓名唯一,入学时间不能为空。
create table students
(
sno char(6) primary key,
sname varchar(10) unique,
ssex char(2) check(ssex in (‘男’,’女’)),
sage smallint,
sdate datetime not null
);
【例8】创建表courses,其中包括课程号(cno),课程名 (cname),学分(credit)3个属性。其中课程号为主键、课程名唯一,学分不能为空。
create table courses
(
cno char(6) primary key,
cname varchar(20) unique,
credit int not null
);
【例9】创建学生选课表SC,其中包括学号(sno)、课程号(cno)和成绩(grade)。学号和课程号共同作为主键。
create table sc
(
sno char(6),
cno char(6),
grade smallint,
primary key (sno,cno),
foreign key (sno) references students(sno),
foreign key (cno) references courses(cno)
);
完整性约束命名子句
前面定义的各种完整性约束都是一次性的,即定义出来以后就不能再改变,比如修改或删除。之所以会这样的原因,就是这些约束没有自己的名称。这就好像你创建了一个数组,却没有给它命名一样,你让后面的代码如何访问它呢?因此完整性约束命名子句的作用就是在定义约束的同时给它一个名称,那么你就可以通过这个名字来访问这些约束,从而对它们进行相应的维护操作。
约束与数据库中的表和视图一样,可以进行增、删、改的更新操作。为了修改和删除约束,需要在定义约束时对其进行命名,在各种约束的说明前加上关键字constraint 和该约束的名称即可。
【例1】建立学生登记表Student,要求学号在90000~99999之间,姓名不能取空值,年龄小于30,性别只能是“男”或“女”。
create table Student
(Sno int(6)
constraint C1 check (Sno between 90000 and 99999),
Sname char(20)
constraint C2 not null,
Sage smallint(3)
constraint C3 check (Sage < 30),
Ssex char(2)
constraint C4 check (Ssex in ( '男','女')),
constraint StudentKey primary key(Sno)
);
【例2】修改表Student中的约束条件,要求学号改为在800000~999999之间;年龄由小于30改为小于40;可以先删除原来的约束条件,再增加新的约束条件。
alter table Student
drop constraint C1;
alter table Student
add constraint C1 check(Sno between 800000 and 999999),
alter table Student
drop constraint C3;
alter table Student
add constraint C3 check(Sage<40);