2021-4-19课程——第5章例题【续】触发器+存储过程和函数

本篇总结今日课堂内容,包括触发器、存储过程与函数部分,第5章内容彻底结束。
一、触发器

触发器(trigger)是用户定义在关系表上的一类由事件驱动的特殊过程。一旦定义,触发器将被保存在数据库服务器中。任何用户对表的增删改操作均由服务器自动激活相应的触发器。触发器可以实施更为复杂的检查和操作,具有更精细和更强大的数据控制能力。

1.定义触发器

触发器又叫做事件-条件-动作规则。当特定事件发生时,对规则的条件进行检查:条件成立则执行规则中的动作,否则不执行。规则中的动作体通常是一段SQL存储过程。
一般格式如下:

CREATE TRIGGER <触发器名> – 触发事件发生时,该触发器就被激活
{BEFORE | AFTER} <触发事件> ON <表名> – 指明触发器激活的时间是在执行触发事件前或后
REFERENCING NEW|OLD ROW AS<变量> – 指出引用的变量
FOR EACH {ROW | STATEMENT} – 定义触发器的类型,指明动作体执行的频率
[WHEN <触发条件>]<触发动作体> – 仅当触发条件为真时才执行触发动作

触发事件
触发事件可以是INSERT、DELETE或UPDATE,也可以是这几个事件的组合,还可以UPDATE OF<触发列,…>,即进一步指明修改哪些列时激活触发器

AFTER/BEFORE是触发的时机
AFTER表示在触发事件的操作执行之后激活触发器
BEFORE表示在触发事件的操作执行之前激活触发器

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

例如,在例5.11的TEACHER表上创建一个AFTER UPDATE触发器,触发事件是UPDATE语句:UPDATE TEACHER SET Deptno=5;假设表TEACHER有1000行,如果是语句级触发器,那么执行完该语句后,触发动作只发生1次(就一个SQL语句)。如果是行级触发器,触发动作将执行1000次(每一行都会触发一次)

例:当对表SC的Grade属性进行修改时,若分数增加了10%,则将此次操作记录到另一个表SC_U(Sno,Cno,Oldgrade,Newgrade)中,其中Oldgrade是修改前的分数,Newgrade是修改后的分数。

首先要创建SC_U表
create table SC_U (
	Sno char(9),
	Cno char(4),
	Oldgrade smallint,  /*因为SC表中Grade数据类型为SMALLINT,所以这里与其相同设置*/
	Newgrade smallint
);
定义触发器
create trigger SC_T
after update of Grade on SC
referencing
 oldrow as OldTuple,
 newrow 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);

报错,提示
在这里插入图片描述
百度搜索,发现了上届一个学长(我还认识他。。)的博客,写的很详细,这里我就借来用了

吐血整理】数据库的完整性

T-SQL可创建 DML、DDL 或登录触发器(分为三种)

触发器是一种特殊类型的存储过程,在数据库服务器中发生事件时自动运行。
① 如果用户尝试通过数据操作语言 (DML) 事件修改数据,DML 触发器运行。 DML 事件是针对表或视图的 INSERTUPDATEDELETE 语句。 此类触发器在任何有效事件触发时触发,无论表行是否受影响。
② DDL 触发器是为了响应各种数据定义语言 (DDL) 事件而运行。 这些事件主要对应于 Transact-SQL CREATEALTERDROP 语句,以及执行类似 DDL 操作的某些系统存储过程。
③ 登录触发器是为了响应在建立用户会话时触发的 LOGON 事件而触发。
可以直接使用 Transact-SQL 语句创建触发器,也可以使用程序集方法,可以为任何特定语句创建多个触发器。
— — — — — — — — — — — — — — — — — — — — —
该例题中涉及的是DML事件,所以应创建DML触发器,可使用SSMS和T-SQL两种办法来创建

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

采用T-SQL方式,注意T-SQL中声明变量的时候一定要加@符号,类似于数学模型matlab中的句柄

deleted 和 inserted 表保存了可能会被用户更改的行的旧值或新值。
create trigger SC_T
on SC
for update
as             /*声明变量*/
	declare @OLD smallint
	declare @NEW smallint
	declare @SNO char(9)
	declare @CNO char(4)
if(update(Grade))
begin
	select @OLD = Grade from deleted
	select @NEW = Grade from inserted
	select @SNO = Sno from deleted
	select @CNO = Cno from deleted
	if(@NEW >= 1.1 * @OLD)
	insert into SC_U(Sno, Cno, Oldgrade, Newgrade)
	values (@SNO, @CNO, @OLD, @NEW)
end;
查看结果,由于是对基本表SC建立的触发器,所以是表触发器,查看方式如下(建议刷新一下,不然缓存等原因无法直接实现,不像idea自动更新和保存)

在这里插入图片描述

点击SC_T旁边的小图标还可以发现我们之前写的语句,也就是定义触发器时的语句

在这里插入图片描述

下面来测试一下

SC表的本来面目

在这里插入图片描述

update SC
set Grade = 90
where Sno='201215121' and Cno='1';

update SC
set Grade = 98
WHERE Sno='201215121' and Cno='2';

select * from SC;
select * from SC_U;

在这里插入图片描述

说明执行成功,针对第一条修改,由于不满足触发器的执行条件(新的成绩大于等于旧成绩的1.1倍),所以并未触发触发器,没有进入,而第二条修改语句满足条件,所以在SC_U表有记录!

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

-- 新建一个StudentInsertLog存储学生人数
create table StudentInsertLog (
	Numbers int
)
-- 新建表存储用户名和操作时间
create table StudentInsertLogUser (
	UserName nchar(10),
	DateAndTime datetime
)
-- 新建触发器Student_Count
create trigger Student_Count
after insert on Student  
referencing
new table as DELTA
for each statement  
insert into StudentInsertLog(Numbers)
select count(*) from DELTA;-- 查询有多少条数据

PS:不能同时执行所有语句,会报错。应该分开执行,先建立表。
在这里插入图片描述

同上一个例子,执行定义触发器部分的命令会报错,更改为T-SQL标准格式下的语句,如下。参考老师博客的链接

【Transact-SQL】教材 [例5.22] “触发器”(TRIGGER)

-- Student_Count触发器,
-- 当插入新的学生记录时,触发器启动,自动在StudentInsertLog记录学生人数
create trigger Student_Count
on Student  	         
after
insert
as 
    insert into StudentInsertLog(Numbers)
	select count(*) from Student;

-- Student_Time触发器
-- 当插入新的学生记录时,触发器启动,自动在StudentInsertLogUser记录用户名和操作时间
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)

	insert into StudentInsertLogUser(UserName,DateAndTime)
	values(@UserName,@DateTime);
下面进行测试
select * from Student; -- 查看插入之前的Student表中的数据内容

insert into Student -- 插入一条数据
values('201215135','王五','男',18,'CS');
-- 查询
select * from Student;
select * from StudentInsertLog;
select * from StudentInsertLogUser;

在这里插入图片描述

== 例3:定义一个before行级触发器,为教师表Teacher定义完整性规则“教授的工资不得低于4000元,如果低于4000元,自动改为4000元”==

首先要建立Teacher表,这里该表的建立我参考了第5章5.4用户自定义完整性中的例子
create table Teacher
(Eno numeric(4) primary key, -- 教职工ID
 Ename char(10), -- 姓名
 Job char(8), -- 具体工作
 Sal numeric(7,2), -- 工资
 Deduct numeric(7,2), -- 扣除费用
 Deptno numeric(2), -- 部门编号
);

插入一些数据,最终如下

在这里插入图片描述

-- 标准SQL
create trigger Insert_Or_Update_Sal 
before insert or update on Teacher   /*触发事件是插入或更新操作*/
for each row        /*行级触发器*/
begin                    /*定义触发动作体,是PL/SQL过程块*/
	if (new.Job = '教授') and (new.Sal < 4000) 
	then  new.Sal := 4000;
	end if;
end;
仍然是会出错的,下面是T-SQL中的写法,我喜欢先declare
create trigger Insert_Or_Update_Sal
on Teacher
for update,insert
as
	declare @JOB char(9)
	declare @SAL smallint

if update(Sal)
begin	
	select @SAL = Sal from inserted
	select @JOB = Job from Teacher
	if(@SAL < 4000 and @JOB = '教授')
	update Teacher
	set SAL = 4000
	where Sal < 4000 and Job = '教授'
end;
下面进行测试,插入一条数据
insert into Teacher
values(1004,'小王','教授',3600,0,4);

在这里插入图片描述

成功!因为小王是教授,插入的值本来是3600,小于4000,满足触发器激活的条件,所以他的Sal变成了4000。
2.激活触发器

触发器的执行,是由触发事件激活的,并由数据库服务器自动执行!
一个数据表上可能定义了多个触发器,遵循如下的执行顺序:
(1) 执行该表上的BEFORE触发器;
(2) 激活触发器的SQL语句;
(3) 执行该表上的AFTER触发器。

3.删除触发器

语法如下:

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

该触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除。

例:删除Insert_Or_Update_Sal触发器

drop trigger Insert_Or_Update_Sal on Teacher;

报错,如下:
在这里插入图片描述
在T-SQL中是不能这样写的,参考官方文档和老师上课讲的:官方文档

drop trigger Insert_Or_Update_Sal;
-- 或者这样写,下面这种写法更好,类似删表,如果存在才删除
if(object_id('Insert_Or_Update_Sal') is not null)
	drop trigger Insert_Or_Update_Sal;
出现以下语句说明删除成功!
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419133808306.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTg0NTAzOQ==,size_16,color_FFFFFF,t_70#pic_center)
二、存储过程和函数

这一章本来是第8章,由于和本节相关,所以老师放在一起讲了,因此例题也就都放在了第5章里。

引入
过程化SQL块主要有两种类型,即命名块和匿名块,之前学的都是匿名块,匿名块每次执行时都要进行编译,它不能被存储到数据库中,也不能在其他过程化SQL块中调用。过程和函数是命名块,它们被编译后保存在数据库中,称为持久性存储模块,可以被反复调用,运行速度较快。

存储过程
1.概念及优点

存储过程:由过程化SQL语句,经编译和优化后存储在数据库服务器中,可以被反复调用,运行速度较快。

优点:
(1)运行效率高
(2)降低了客户机和服务器之间的通信量
(3)方便实施企业规则

2.存储过程的用户接口
(1)创建存储过程

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

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

①首先要有账户表Account,建立并插入两条数据

drop table if exists Account;

create table Account(
	accountnum char(3),-- 编号
	total float -- 余额
);

insert into Account
values('101',50),('102',100);

select * from Account;

在这里插入图片描述
②创建存储过程,具有健壮性

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

此时查看存储过程的方式和触发器是不同的,前边的触发器都是基于基本表的,而存储过程是基于数据库的,所以应该在当前数据库下寻找。

在这里插入图片描述

(2)执行存储过程,T-SQL是用exec,标准SQL是call或者perform

①101向102转账50,转完后,101账户余额为0

select * from Account;

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

select * from Account;

在这里插入图片描述
在这里插入图片描述

②101继续向102转账50元,SQL语句同上,此时应该输入账户余额不足
在这里插入图片描述
在这里插入图片描述
③测试账户不存在

exec Proc_TRANSFER
	@inAccount = 110,	--转入账户
	@outAccount = 102,	--转出账户
	@amount = 100		--转出金额

select * from Account;

在这里插入图片描述
测试成功!

(3)修改存储过程

ALTER { PROC | PROCEDURE } [schema_name.] procedure_name
[ { @parameterdata_type } [= ] ] [ ,…n ]
AS { [ BEGIN ] sql_statement [ ; ] [ ,…n ] [ END ] }
[;]

具体操作为:只需将创建存储过程时的create改为alter即可,改动需要改动的地方,其他不变。

(4)删除存储过程

drop procedure 过程名;

例:删除刚刚建立的存储过程

drop procedure Proc_TRANSFER;

在这里插入图片描述

3.函数
函数和存储过程的异同

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

下面是标准SQL函数的相关内容
1.函数的定义语句格式

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

2.函数的执行语句格式

CALL/SELECT 函数名 ([参数1,参数2,…]);

3.修改函数
重命名

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

重新编译

ALTER FUNCTION 过程名 COMPILE;

T-SQL标准参考T-SQL中的函数
例:求男女学生的平均年龄
①定义函数

create function AvgAge(@Sex varchar(10)='男')    --在这里是给参数默认值,即可以不传参数调用这个函数
returns decimal
as
begin
    declare @AvgAge decimal(10,2)
    select @AvgAge=AVG(Convert(decimal(10,2),Sage)) from Student where Ssex=@Sex
    return @AvgAge
end;

在这里插入图片描述

②测试使用

-- select [函数的所有者].函数名 as 字段别名
 
select dbo.AvgAge(default) as AvgAge;
select dbo.AvgAge('男');
select dbo.AvgAge('女');
这里一定要写函数所有者,默认是dbo,否则会报错!!!

在这里插入图片描述

三、课程感悟
这一部分很难理解,而且理解了,相关的SQL语句也很难编写,感觉没什么规律似的,再加上T-SQL与标准SQL的区别,更是“火上浇油”。不过好在,都找到了解决办法。又结束了一章,老师让写一个这段学习的总结,希望通过总结可以让自己重新记起学过的东西并对它们有更深的理解。加油!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45845039/article/details/115858281