第五章 数据库完整性
最近在准备实习面试的事,开始复习数据库,用到的书是中国人民大学出版社出版的《数据库系统概论》,特地在此做一下笔记,勉励一下自己。
附上视频链接,王珊老师讲的数据库系统概论:
http://163.lu/SQTKY3
http://163.lu/XJKIH2
http://163.lu/Z2Var2
-
数据库的完整性是为了保证数据库中存储的数据是正确的。
-
关系数据库管理系统完整性实现的机制
(1)完整性约束定义机制
实体完整性
参照完整性
用户定义的完整性
(2)完整性检查机制
完整性约束命名子句
断言
(3)违背完整性约束条件时关系数据库管理系统应采取的动作
触发器
一、 完整性约束命名子句
1.完整性约束命名子句
-
格式:
CONSTRAINT <完整性约束条件名><完整性约束条件>
<完整性约束条件>包括NOT NULL、UNIQUE、PRIMARY KEY短语、FOREIGN KEY短语、CHECK短语等。 -
例子1:
建立学生登记表Student,要求学号在90000~99999之间,姓名不能取空值,年龄小于30,性别只能是“男”或“女”。
create table Student
( Sno numeric(6)
constraint C1 check (Sno between 9000 and 99999),
Sname char(20)
constraint C2 not null,
Sage numeric(6)
constraint C3 check (Sage<30),
Ssex char(2)
constraint C4 check (Ssex in ('男','女')),
constraint StudentKey primary key(Sno)
);
在Student表上建立了5个约束条件,包括主码约束(命名为StudentKey)以及C1、C2、C3、C4四个列级约束。
2.修改表中的完整性限制
-
格式: 使用ALTER TABLE语句修改表中的完整性限制。
-
例子2:去掉例5.10 Student表中对性别的限制。
alter table Student
drop constraint C4; -
例子3:修改表Student中的约束条件,要求学号改为在900000~999999之间,年龄由小于30改为小于40。
-
方式:SQL语句中是不能直接修改完整性约束命名子句,可以先删除原来的约束条件,再增加新的约束条件。
alter table Student
drop constraint C1;
alter table Student
add constraint C1 check (Sno between 900000 and 999999);
alter table Student
drop constraint C3;
alter table Student
add constraint C3 check (Sage<40);
二、 断言
SQL中,可以使用 CREATE ASSERTION语句,通过声明性断言来指定更具一般性的约束。可以定义涉及多个表的或聚集操作的比较复杂的完整性约束。断言创建以后,任何对断言中所涉及的关系的操作都会触发关系数据库管理系统对断言的检查,任何使断言不为真值的操作都会被拒绝执行。
1.定义断言
- 格式:CREATE ASSERTION <断言名><CHECK 子句>
每个断言都被赋予一个名字,<CHECK 子句>中的约束条件与WHERE子句的条件表达式类似。 - 例子4:限制数据库课程最多60名学生选修。
这个题目涉及课程表上的课程名和选课表上的学生选课数目,在一般的完整性约束中,只涉及单个表的元组和属性的约束,并不能定义多个表上的约束。采用断言可以解决这个问题。
create assertion ASSE_SC_DB_NUM
check(60>=(select count(*)
/*此断言的谓词涉及聚集操作count的SQL语句*/
from Course,SC
where SC.Cno=Course.Cno and Course.Cname='数据库')
);
- 例子5:限制每个学期每一门课程最多60名学生选修。
由于check子句与where子句相类似,我们可以在check中使用group by子句。
由于SC表没有学期属性,首先需要修改SC表的模式,增加一个“学期(TERM)”属性。
alter table SC
add TERM date
然后定义断言:
create assertion ASSE_SC_CNUM2
check(60>=all(select count(*)
from SC
group by Cno,TERM)
);
2.删除断言
如果断言很复杂,在系统在检测或维护断言的开销过高,这是在使用断言时应该注意的
- 格式:DROP ASSERTION <断言名>
三、 触发器
为什么要有触发器这个东西?
因为前面我们的完整性约束和断言只能检查操作语义上的正确与否,正确的话,操作正常执行;不正确的话,操作就被拒绝执行了,什么也不做。
没有解决的问题,当操作不符合完整性约束时,能不能不拒绝,而是采取另外一些操作来弥补,这就需要触发器(Trigger)。
1.定义触发器
触发器(Trigger)是用户定义在关系表上的一类由事件驱动的特殊过程。
(1)触发器保存在数据库服务器中
(2)任何用户对表的增、删、改操作均由服务器自动激活相应的触发器
(3)触发器可以实施更为复杂的检查和操作,具有更精细和更强大的数据控制能力
-
格式:
CREATE TRIGGER <触发器名>
{BEFORE | AFTER} <触发事件> ON <表名>
REFERENCING NEW|OLD ROW AS<变量>
FOR EACH {ROW | STATEMENT}
[WHEN <触发条件>]<触发动作体>触发器又叫事件-条件-动作(event-condition-action)规则。
当特定的系统事件发生时,对规则的条件进行检查,如果条件成立,则执行规则中的动作,否则不执行该动作。规则中的动作体可以很复杂,通常是一段SQL存储过程。
(1)表的拥有者才可以在表上创建触发器
(2)触发器名
触发器名可以包含模式名,也可以不包含模式名。
同一模式下,触发器名必须是唯一的。
触发器名和表名必须在同一模式下。
(3)表名
触发器只能定义在基本表上,不能定义在视图上
当基本表的数据发生变化时,将激活定义在该表上相应触发事件的触发器。
(4)触发事件
触发事件可以是INSERT、DELETE或UPDATE也可以是这几个事件的组合
还可以UPDATE OF<触发列,…>,即进一步指明修改哪些列时激活触发器
(5)触发时间:AFTER/BEFORE是触发的时机
AFTER表示在触发事件的操作执行之后激活触发器
BEFORE表示在触发事件的操作执行之前激活触发器
(6)触发器类型
行级触发器(FOR EACH ROW)
语句级触发器(FOR EACH STATEMENT) -
例子6:在TEACHER表上创建一个AFTER UPDATE触发器,触发事件是UPDATE语句:
update TEACHEER set Deptno=5;
假设表TEACHER有1000行。如果是语句级触发器,那么执行完该语句后,触发动作只发生一次,如果是行级触发器,触发动作将执行1000次。(7)触发条件
触发器被激活时,只有当触发条件为真时触发动作体才执行;否则触发动作体不执行。
如果省略WHEN触发条件,则触发动作体在触发器激活后立即执行。
(8)触发动作体
触发动作体可以是一个匿名PL/SQL过程块,也可以是对已创建存储过程的调用。
如果是行级触发器,用户都可以在过程体中使用NEW和OLD引用事件之后的新值和事件之前的旧值。
如果是语句级触发器,则不能在触发动作体中使用NEW或OLD进行引用。
如果触发动作体执行失败,激活触发器的事件就会终止执行,触发器的目标表或触发器可能影响的其他对象不发生任何变化 。 -
例子7:当对表SC的Grade属性进行修改时,若分数增加了10%则将此次操作记录到下面表中:
SC_U(Sno,Cno,Oldgrade,Newgrade)
其中Oldgrade是修改前的分数,Newgrade是修改后的分数。
create trigger SC_T
alter update of Grade on SC
referencing
old row as oldtuple
new row as newtuple
for each row
when(newtuple.Grade>=1.1*oldtuple.Grade)
insert into SC_U(Sno,Cno,Oldgrade,Newgrade)
values(oldtuple.Sno,oldtuple.Cno,oldtuple.Grade,newtuple.Grade);
- 例子8:将每次对表Student的插入操作所增加的学生个数记录到表StudentInsertLog中。
显然这个含有聚集函数的操作,需要有一个语句级的触发器。
create trigger Student_ Count
alter insert on Student
/* 触发器激活的时间是在执行insert后*/
referencing new table as DELTA
for each statement
/*语句级触发器, 即执行完INSERT语句后下面的触发动作体才执行一次*/
insert into StudentInsertLog(Numbers)
select count(*) from DELTA
以上都是after触发器的例子,下面给出before触发器的例子。
- 例子9:定义一个before行级触发器,为教师表Teacher定义完整性规则“教授的工资不得低于4000元,如果低于4000元,自动改为4000元”。
create trigger Insert_or_Update_Sal
before insert or update on Teacher
for each row
begin
if(new.job='教授') and(new.Sal<4000)
then new.Sal:=4000;
end if;
end;
2.激活触发器
触发器的执行,是由触发事件激活的,并由数据库服务器自动执行
一个数据表上可能定义了多个触发器,遵循如下的执行顺序:
(1) 执行该表上的BEFORE触发器;
(2) 激活触发器的SQL语句;
(3) 执行该表上的AFTER触发器。
3.删除触发器
删除触发器的SQL语法:
drop trigger <触发器名> on <表名>;
触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除。