完整性+存储过程和函数——CHECK / CONSTRAINT / TRIGGER / PROCEDURE/ FUNCTION

数据库完整性

1.实体完整性

CREATE TABLE中用PRIMARY KEY定义,可在列级,表级完整性条件中定义,如【例5.1】。涉及到多属性作为码的时候,只能放在最后表级完整性条件中,如【例5.2】。

规则:主码唯一,不为空
插入和更新时DBMS会根据这一规则进行检查,不唯一或为空就拒绝。

【例5.1】将Student表中的Sno定义为主码

(1)列级定义主码

CREATE TABLE Student
(Sno CHAR(9) PRIMARY KEY,
Sname CHAR(20) NOT NULL,
Ssex CHAR(2),
Sage SMALLINT,
Sdept CHAR(20)
);

(2)表级定义主码

CREATE TABLE Student
(Sno CHAR(9),
Sname CHAR(20) NOT NULL,
Ssex CHAR(2),
Sage SMALLINT,
Sdept CHAR(20),
PRIMARY KEY(Sno)
);

【例5.2】

CREATE TABLE SC
(Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT,
PRIMARY KEY(Sno,Cno)
);

2.参照完整性

在CREATE TABLE中用FOREIGN KEY定义外码,用REFERENCES指明外码参照哪些表中的主码。
定义参照完整性,要放到最后表级完整性约束,因为会涉及到其他列。

【例5.3】定义SC表中的参照完整性

CREATE TABLE SC
(Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT,
PRIMARY KEY(Sno,Cno),
FOREIGN KEY Sno REFERENCES Student(Sno),
FOREIGN KEY Cno REFERENCES Student(Cno)
);

参照完整性规则:外码要么为空,要么取自被参照关系的主码
插入或修改时,DBMS会进行检查,并做出违约处理,其中的违约处理也可以显式给出。
在这里插入图片描述
设置为空值的情况,一般用于外码在该关系中不是主码,并且没有MOT NULL约束的情况,可以设为空值。

【例5.4】显式说明参照完整性的违约处理示例

CREATE TABLE SC
(Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT,
PRIMARY KEY(Sno,Cno),
FOREIGN KEY Sno REFERENCES Student(Sno)
  ON DELETE CASCADE  --级联删除SC表中对应的元组
  ON UPDATE CASCADE, --级联更新SC表中对应的元组
FOREIGN KEY Cno REFERENCES Student(Cno)
  ON DELETE NO ACTION  --拒绝删除
  ON UPDATE CASCADE --级联更新
);

注意在定义外码语句和违约处理语句之间没有逗号,两个违约处理语句之间没有逗号。

扫描二维码关注公众号,回复: 10524572 查看本文章

3.用户定义的完整性

针对某一具体应用的数据必须满足的语义要求

  • 属性上的约束条件

列级完整性
定义:NOT NULL(列值非空),UNIQUE(取值唯一),CHECK(检查列值是否满足条件)

(1)NOT NULL 非空
【例5.5】在定义SC表时,说明Sno、Cno、Grade不为空

CREATE TABLE SC
(Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT NOT NULL,
PRIMARY KEY(Sno,Cno),
...

(2)UNIQUE 取值唯一
【例5.6】建立部门表DEPT,要求部门名称Dname列取值唯一,部门编号Deptno为主码

CREATE TABLE DEPT
(Deptno NUMERIC(2) PRIMARY KEY,
Dname CHAR(9) UNIQUE,
Location CHAR(10)
);

(3)CHECK短语
【例5.7】Student表中的Ssex只能取‘男’或‘女’

CREATE TABLE Student
(Sno CHAR(9) PRIMARY KEY,
Sname CHAR(20) NOT NULL,
Ssex CHAR(2) CHECK (Ssex IN ('男','女')),
Sage SMALLINT,
Sdept CHAR(20)
);

【例5.8】SC表的Grade值应在0到100之间

CREATE TABLE SC
(Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT CHECK (Grade >= 0 AND Grade <= 100),
PRIMARY KEY(Sno,Cno),
FOREIGN KEY Sno REFERENCES Student(Sno),
FOREIGN KEY Cno REFERENCES Student(Cno)
);

CHECK短语相当于WHERE子句,加条件。

  • 元组上的约束条件

在CREATE TABLE 时用CHECK短语定义,元组级的限制,涉及多个列,表级完整性
【例5.9】当学生的性别是男时,其名字不能用‘Ms.’打头

CREATE TABLE Student
(Sno CHAR(9) PRIMARY KEY,
Sname CHAR(8) NOT NULL,
Ssex CHAR(2),
Sage SMALLINT,
Sdept CHAR(20),
CHECK (Ssex='女' OR Sname NOT LIKE 'Ms.%')
--涉及到了两个属性值 Ssex和Sname,对元组的限制
);

学生表中,要么性别为女,要么名字不是Ms.打头的

元组上和属性上的约束条件,在插入、更新元组或属性值时,DBMS都会检查是否满足条件,不满足则拒绝。

4.完整性约束命名子句

命名:

CONSTRAINT <完整性约束条件名> <完整性约束条件>

给约束命名,便于查找,修改和删除。
完整性约束条件可以为 NOT NULL,UNQIUE,CHECK短语,PRIMARY KEY短语,FOREIGN KEY短语等

【例5.10】建立学生登记表Student,要求学号在90000~99999之间,姓名不能取空值,年龄小于30,性别只能是‘男’或‘女’。

CREATE TABLE Student1
(Sno NUMERIC(6) 
 CONSTRAINT C1 CHECK(Sno BETWEEN 90000 AND 99999),
Sname CHAR(20) 
 CONSTRAINT C2 NOT NULL,
Ssex CHAR(2) 
 CONSTRAINT C3 CHECK (Ssex IN ('男','女')),
Sage SMALLINT
 CONSTRAINT C4 CHECK(Sage<30),
CONSTRAINT StudentKey PRIMARY KEY(Sno)
);

为了保留之前的表,这里换了个名称。
在这里插入图片描述

【例5.11】建立教师表TEACHER,要求每个教师的应发工资不低于3000元。应发工资是工资列Sal和扣除项Deduct之和。

CREATE TABLE TEACHER
(Eno NUMERIC(4) PRIMARY KEY,
Ename CHAR(10),
Job CHAR(8),
Sal NUMERIC(7,2),
Deduct NUMERIC(7,2),
Deptno NUMERIC(2),
CONSTRAINT TEACHERKey FOREIGN KEY (Deptno)
   REFERENCES DEPT(Deptno),
CONSTRAINT C5 CHECK(Sal + Deduct >= 3000)
--元组上的约束条件
);

要与上题中建立的Student1在同一个库中的话,最后的约束名应该修改,因为在Student1中已经有了该约束名。
在这里插入图片描述
外码和主码的约束直接标注在列里。

修改:
ALTER TABLE修改表中的完整性限制

【例5.12】去掉5.10中Student1表中的对性别的限制

ALTER TABLE Student1
DROP CONSTRAINT C3;

测试,可以插入除 男、女 之外的值,删除成功。
在这里插入图片描述

【例5.13】修改Student1表中的条件,要求学号改为900000~999999之间,年龄在由小于30改为小于40.

先删除原来的约束条件,再增加新的条件。

ALTER TABLE Student1
DROP CONSTRAINT C1;
ALTER TABLE Student1
ADD CONSTRAINT C1 CHECK(Sno BETWEEN 900000 AND 999999);
ALTER TABLE Student1
DROP CONSTRAINT C4; 
ALTER TABLE Student1
ADD CONSTRAINT C4 CHECK(Sage < 40);

这里的删除可以一起删除,添加也可以一起添加:

ALTER TABLE Student1
DROP CONSTRAINT C1,C4;
ALTER TABLE Student1
ADD CONSTRAINT C1 CHECK(Sno BETWEEN 900000 AND 999999),
    CONSTRAINT C4 CHECK(Sage < 40);

在这里插入图片描述
语句是没有错误的,这里会出现错误是因为在5.12中测试的时候添加了学号为90001的记录,如果修改约束性条件,使得表中的数据不满足,则拒绝添加这个约束性条件

5.触发器

定义触发器:
语句:

CREATE TRIGGER <触发器名>
{BEFORE|AFTER} <触发事件> ON <表名>
REFERENCING NEW|OLD ROW AS <变量>
FOR EACH {ROW|STATEMENT}
[WHEN <触发条件>]<触发动作体>

当特定的系统事件发生时,对规则的条件进行检查,如果条件成立则执行规则中的动作。
1.AFTER|BEFORE是触发的时机。
2.ON 后的<表名> 表示是建立在哪个表上的触发器。
3.AS后的变量,相当于给NEW ROW或者OLD ROW起的别名,便于在后面的触发条件和动作体中使用。
4.触发事件可以是INSERT/UPDATE/DELETE,也可以是他们的组合。还可以用UPDATE OF <触发列,…>,指明修改哪些列时触发
5.触发器类型:

  • 行级触发器(FOR EACH ROW)
  • 语句级触发器(FOR EACH STATEMENT)

在这里插入图片描述
【例5.21】当对表SC的Grade属性进行修改时,若分数增加了10%,则将此次操作记录到SC_U表中:
SC_U(Sno,Cno,Oldgrade,Newgrade)

--1.创建SC_U表:
CREATE TABLE SC_U
(Sno CHAR(10) NOT NULL,
Cno CHAR(5) NOT NULL,
Oldgrade SMALLINT
 CONSTRAINT C6 CHECK(Oldgrade BETWEEN 0 AND 100),
Newgrade SMALLINT
 CONSTRAINT C7 CHECK(Newgrade BETWEEN 0 AND 100),
CONSTRAINT SCKEY PRIMARY KEY(Sno,Cno)
);

--2.定义触发器:
CREATE TRIGGER SC_T
AFTER 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
  VALUES(OldTuple.Sno,OldTuple.Cno,OldTuple.Grade,NewTuple.Grade);

【例5.22】将每次对表Student的插入操作所增加的学生个数记录到表StudentInsertLog中。

--1.建表
CREATE TABLE StudentInsertLog
(Numbers INT
);

标准SQL:

CREATE TRIGGER Student_Count
AFTER INSERT ON Student
REFERENCING
   NEW TABLE AS DELTA
FOR EACH STATEMENT
   INSERT INTO StudentInsertLog
   SELECT COUNT(*) FROM DELTA
   --带子查询的插入,将子查询结果插入到表中

这里插入的是新表DELTA的元组个数,指的是增加的个数。

T-SQL:

创建触发器,记录学生人数:

CREATE TRIGGER Student_Count
ON Student  	         
AFTER
INSERT
AS 
    INSERT INTO StudentInsertLog(Numbers)
	  SELECT COUNT(*) FROM Student

建立一个存储用户名和时间的表,相当于审计日志。
StudentInsertLogUser

CREATE TABLE StudentInsertLogUser
( UserName nchar(10),
  DateAndTime datetime
);

创建触发器,记录用户名和操作时间

CREATE TRIGGER Student_Time
ON Student  	         
AFTER
INSERT
AS 
	declare @UserName    nchar(10)
	declare @DateTime    datetime
	--相当于定义变量,变量名前要加@

	select @UserName = system_user
	select @DateTime = CONVERT(datetime,GETDATE(),120) --2018-04-11 16:33:10

	INSERT INTO StudentInsertLogUser(UserName,DateAndTime)
	VALUES (@UserName,@DateTime)

DECLARE定义变量
SELECT取系统的值
INSERT语句将值插入

测试:

INSERT
INTO  Student
VALUES ('201215998','小龙人','男',20,'MA');
SELECT * FROM Student;
SELECT * FROM StudentInsertLog;
SELECT * FROM StudentInsertLogUser;

在这里插入图片描述
在这里插入图片描述
这里统计的是整个Student表中有多少元组。

【例5.23】定义一个BEFORE行级触发器,为教师表定义完整性规划“教授的工资不能低于4000元,如果低于,自动改为4000元”

建表:

CREATE TABLE Teacher1
(Tno CHAR(10) PRIMARY KEY,
Tname CHAR(10) UNIQUE,
Sal INT,
Job CHAR(10)
);

标准SQL:

CREATE TRIGGER Insert_Or_Update_Sal
BEFORE INSERT OR UPDATE ON Teacher1
--触发事件为插入或更新
FOR EACH ROW  --行级触发器
BEGIN
   IF(new.Sal < 4000) AND (new.Job = '教授')
      THEN new.Sal=4000;
   END IF;
END;

触发动作体为BEGIN…END块,里面相当于C语言,IF…END IF相当于IF语句,满足条件执行THEN语句。这里没有REFERENCING语句,new就是新元组。

T-SQL:

CREATE TRIGGER Insert_Or_Update_Sal
ON Teacher1
FOR INSERT,UPDATE
AS
   declare @number CHAR(10)
   declare @name CHAR(10)
   declare @salary INT
   declare @job CHAR(10)

   select @salary = Sal FROM inserted
   select @number = Tno FROM inserted
   select @name = Tname FROM inserted
   select @job = Job FROM inserted
   --更新或者插入,改变的新行都会存入到INSERTED表中
   
IF(@salary < 4000 AND @job='教授')
BEGIN
  IF EXISTS(SELECT * FROM Teacher1 WHERE Tno = @number)
     UPDATE Teacher1 SET Sal = 4000 WHERE Tno = @number
	 --如果之前有数据,更新工资
  IF NOT EXISTS(SELECT * FROM Teacher1 WHERE Tno = @number)
     BEGIN
     INSERT INTO Teacher1
     VALUES(@number,@name,4000,@job)
	 --否则插入数据
	 END
END

INSERT INTO Teacher1 VALUES('001','王晓易',3500,'教授');
INSERT INTO Teacher1 VALUES('002','张甜甜',2000,'教师');
INSERT INTO Teacher1 VALUES('003','李小李',6000,'教授');

SELECT * FROM Teacher1;

UPDATE Teacher1
SET Sal= 3000
WHERE Tno = '003';

SELECT * FROM Teacher1;

执行插入语句后:
在这里插入图片描述
执行更新语句后:
在这里插入图片描述
T-SQL用FOR和AFTER。
DML 触发器语句使用两种特殊的表:删除的表deleted和插入的表inserted
在这里插入图片描述
inserted表,插入表,存放插入的元组,是新行的副本。
deleted表,删除表,存放被删除的元组。
(以上内容摘自T-SQL官方文档)

激活触发器

触发器由触发事件激活,并由数据库服务器自动执行
含有多个触发器的执行顺序:
(1)BEFORE级触发器
(2)激活触发器上的SQL语句
(3)AFTER级触发器

删除触发器

DROP TRIGGER <触发器名> ON <表名>;

存储过程和函数

1.存储过程 —— PROCEDURE

由过程化SQL语句,经编译和优化后存储在数据库服务器中,可以被反复调用,运行速度较快。
相当于编写了一个函数库,方便调用。

创建存储过程:

CREATE OR REPLACE PROCEDURE 过程名([参数1,参数2,...])
AS <过程化SQL>;

【例8.8】利用存储过程来实现下面的应用:从账户1转指定数额的款项到账户2中。

建立新表Account并插入两个账户:

CREATE TABLE Account
(
accountnum CHAR(3),	-- 账户编号
total FLOAT		-- 账户余额
);

INSERT INTO Account VALUES(101,50);
INSERT INTO Account VALUES(102,100);

SELECT * FROM Account;

在这里插入图片描述

需要
1.检查账户1余额是否足够
2.账户1,2是否存在

标准SQL:

CREATE OR REPLACE PROCEDURE TRANSFER(inAccount INT,outAccount INT,amount FLOAT)--存储过程及其参数
AS DECLARE
  totalDepositOut Float;
  totalDepositIn Float;
  inAccountnum INT; --定义变量
BEGIN
  --检查转出账户的余额
  SELECT total INTO totalDepositOut FROM Account
  WHERE Accountnum = outAccount;--将Account表中的转出账户的余额赋给对应变量
  IF totalDepositOut IS NULL
    THEN 
      ROLLBACK;
      RETURN
  END IF; --转出账户不存在,回滚
  IF totalDepositOut < amount
    THEN
      ROLLBACK;
      RETURN
  END IF; --账户余额不足,回滚
SELECT Accountnum INTO inAccountnum FROM Account
WHERE Accountnum = inAccount;

IF inAccount IS NULL THEN
   ROLLBACK;
   RETURN
END IF;

UPDATE Account SET total=total-amount
 WHERE accountnum = outAccount; --转出
UPDATE Account SET total = total + amount
 WHERE accountnum = inAccount; --转入
COMMIT;--提交转账事务
END;

T-SQL:

IF (exists (select * from sys.objects where name = 'Proc_TRANSFER'))
    DROP PROCEDURE Proc_TRANSFER  --有该存储过程就先删除
GO
CREATE PROCEDURE Proc_TRANSFER 
@inAccount INT,@outAccount  INT,@amount FLOAT
 /*定义存储过程TRANSFER,参数为转入账户、转出账户、转账额度*/
AS
BEGIN TRANSACTION TRANS   
   	DECLARE		/*定义变量*/
	@totalDepositOut Float,
	@totalDepositIn Float,
	@inAccountnum INT;
	 /*检查转出账户的余额 */     
	SELECT @totalDepositOut = total FROM Account	WHERE accountnum = @outAccount;
	/*如果转出账户不存在或账户中没有存款*/
	IF @totalDepositOut IS NULL               	   
		BEGIN
			PRINT '转出账户不存在或账户中没有存款'
			ROLLBACK TRANSACTION TRANS; 	   /*回滚事务*/
			RETURN;
		END;
	/*如果账户存款不足*/
	IF @totalDepositOut < @amount     	
		BEGIN
			PRINT '账户存款不足'
			ROLLBACK TRANSACTION TRANS; /*回滚事务*/
			RETURN;
		END
	/*检查转入账户的状态 */  
	SELECT @inAccountnum = accountnum  FROM Account	WHERE accountnum = @inAccount;
	/*如果转入账户不存在*/ 
	IF @inAccountnum IS NULL   		                       
		BEGIN
			PRINT '转入账户不存在'
			ROLLBACK TRANSACTION TRANS;/*回滚事务*/
			RETURN;
		END;
	/*如果条件都没有异常,开始转账。*/ 
	BEGIN
		UPDATE Account SET total = total - @amount	WHERE	accountnum = @outAccount; /* 修改转出账户余额,减去转出额 */
		UPDATE Account SET total = total + @amount	WHERE   accountnum = @inAccount; /* 修改转入账户余额,增加转入额 */
		PRINT '转账完成,请取走银行卡'
		COMMIT TRANSACTION TRANS;                       	/* 提交转账事务 */
		RETURN;
	END

变量要加@。

执行测试:
1.转入转出都存在的情况下,且余额充足:

EXEC	Proc_TRANSFER
		@inAccount = 101,	--转入账户
		@outAccount = 102,	--转出账户
		@amount = 50		--转出金额

SELECT * FROM Account

在这里插入图片描述
2.转入或转出账户不存在:
在这里插入图片描述
余额不会发生变化:
在这里插入图片描述
3.余额不足:
在这里插入图片描述
执行存储过程:

CALL/PERFORM PROCEDURE 过程名([参数1,参数2,...]);

类似于函数的调用,过程体中还可以调用其他存储过程,类似于函数的嵌套。

【例8.9】从账户01003815868转10000元到01003813828账户中。

标准SQL:

CALL PROCEDURE TRANSFER(01003813828,01003815868,10000)

T-SQL:

--先添加账户
INSERT INTO Account VALUES('01003815868','20000');
INSERT INTO Account VALUES('01003813828','1500');
EXEC Proc_TRANSFER
    @inAccount = 01003813828,
    @outAccount = 01003815868,
    @amount = 10000

在这里插入图片描述
标准SQL用CALL/PERFORM
T-SQL用EXEC,见上。

修改存储过程:

ALTER PROCEDURE 过程名1 RENAME TO 过程名2;

删除存储过程:

DROP PROCEDURE 过程名();

2.函数

函数和存储过程的异同:
同——都是持久性存储模块
异——函数必须指定返回值类型

函数定义:

CREATE OR REPLACE FUNCTION 函数名([参数1,参数2,...])
RETURNS <类型>
AS <过程化SQL>;

比存储过程就多了一个RETURNS返回类型

函数执行:

CALL/SELECT 函数名([参数...])

修改函数:

--重命名
ALTER FUNCTION 函数名1 RENAME TO 函数名2
--重新编译
ALTER FUNCTION 函数名 COMPILE 

【心得】
用T-SQL和标准SQL写触发器的时候,区别挺大,参照老师给出的【例5.22】,自己尝试写了【例5.23】的T-SQL版本,经过多次修改和查资料后运行成功,费时较多。写到后面,触发器和存储过程竟然有些混。

发布了15 篇原创文章 · 获赞 17 · 访问量 5080

猜你喜欢

转载自blog.csdn.net/fu_GAGA/article/details/105271725