本文共 9171 字,大约阅读时间需要 30 分钟。
触发器(trigger)是用户定义在关系表上的一类由事件驱动的特殊过程。一旦定义,触发器将被保存在数据库服务器中。任何用户对表的增删改操作均由服务器自动激活相应的触发器。触发器可以实施更为复杂的检查和操作,具有更精细和更强大的数据控制能力。
触发器又叫做事件-条件-动作规则。当特定事件发生时,对规则的条件进行检查:条件成立则执行规则中的动作,否则不执行。规则中的动作体通常是一段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是修改后的分数。
create table SC_U ( Sno char(9), Cno char(4), Oldgrade smallint, /*因为SC表中Grade数据类型为SMALLINT,所以这里与其相同设置*/ Newgrade smallint);
create trigger SC_Tafter update of Grade on SCreferencing oldrow as OldTuple, newrow as NewTuplefor each rowwhen(NewTuple.Grade>=1.1*OldTuple.Grade) insert into SC_U(Sno,Cno,OldGrade,NewGrade) values(OldTuple.Sno,OldTuple.Cno,OldTuple.Grade,NewTuple.Grade);
报错,提示
触发器是一种特殊类型的存储过程,在数据库服务器中发生事件时自动运行。
① 如果用户尝试通过数据操作语言 (DML) 事件修改数据,DML 触发器运行。 DML 事件是针对表或视图的INSERT
、UPDATE
或 DELETE
语句。 此类触发器在任何有效事件触发时触发,无论表行是否受影响。 ② DDL 触发器是为了响应各种数据定义语言 (DDL) 事件而运行。 这些事件主要对应于 Transact-SQL CREATE
、ALTER
和 DROP
语句,以及执行类似 DDL 操作的某些系统存储过程。 ③ 登录触发器是为了响应在建立用户会话时触发的 LOGON 事件而触发。 可以直接使用 Transact-SQL 语句创建触发器,也可以使用程序集方法,可以为任何特定语句创建多个触发器。 — — — — — — — — — — — — — — — — — — — — — 该例题中涉及的是DML事件,所以应创建DML触发器,可使用SSMS和T-SQL两种办法来创建 采用T-SQL方式,注意T-SQL中声明变量的时候一定要加@符号,类似于数学模型matlab中的句柄
create trigger SC_Ton SCfor updateas /*声明变量*/ 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;
update SCset Grade = 90where Sno='201215121' and Cno='1';update SCset Grade = 98WHERE 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_Countcreate trigger Student_Countafter insert on Student referencingnew table as DELTAfor each statement insert into StudentInsertLog(Numbers)select count(*) from DELTA;-- 查询有多少条数据
PS:不能同时执行所有语句,会报错。应该分开执行,先建立表。
-- Student_Count触发器,-- 当插入新的学生记录时,触发器启动,自动在StudentInsertLog记录学生人数create trigger Student_Counton Student afterinsertas insert into StudentInsertLog(Numbers) select count(*) from Student;-- Student_Time触发器-- 当插入新的学生记录时,触发器启动,自动在StudentInsertLogUser记录用户名和操作时间create trigger Student_Timeon Studentafterinsertas 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元”==
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), -- 部门编号);
插入一些数据,最终如下
-- 标准SQLcreate 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;
create trigger Insert_Or_Update_Salon Teacherfor update,insertas declare @JOB char(9) declare @SAL smallintif 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 Teachervalues(1004,'小王','教授',3600,0,4);
触发器的执行,是由触发事件激活的,并由数据库服务器自动执行!
一个数据表上可能定义了多个触发器,遵循如下的执行顺序: (1) 执行该表上的BEFORE触发器; (2) 激活触发器的SQL语句; (3) 执行该表上的AFTER触发器。语法如下:
DROP TRIGGER<触发器名> ON <表名>
该触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除。
例:删除Insert_Or_Update_Sal触发器
drop trigger Insert_Or_Update_Sal on Teacher;
报错,如下:
drop trigger Insert_Or_Update_Sal;-- 或者这样写,下面这种写法更好,类似删表,如果存在才删除if(object_id('Insert_Or_Update_Sal') is not null) drop trigger Insert_Or_Update_Sal;
这一章本来是第8章,由于和本节相关,所以老师放在一起讲了,因此例题也就都放在了第5章里。
引入
过程化SQL块主要有两种类型,即命名块和匿名块,之前学的都是匿名块,匿名块每次执行时都要进行编译,它不能被存储到数据库中,也不能在其他过程化SQL块中调用。过程和函数是命名块,它们被编译后保存在数据库中,称为持久性存储模块,可以被反复调用,运行速度较快。存储过程:由过程化SQL语句,经编译和优化后存储在数据库服务器中,可以被反复调用,运行速度较快。
优点:
(1)运行效率高 (2)降低了客户机和服务器之间的通信量 (3)方便实施企业规则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 Accountvalues('101',50),('102',100);select * from Account;
if (exists (select * from sys.objects where name = 'Proc_TRANSFER')) drop procedure Proc_TRANSFERgocreate procedure Proc_TRANSFER @inAccount int,@outAccount int,@amount float /*定义存储过程TRANSFER,参数为转入账户、转出账户、转账额度*/asbegin 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
此时查看存储过程的方式和触发器是不同的,前边的触发器都是基于基本表的,而存储过程是基于数据库的,所以应该在当前数据库下寻找。
①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;
ALTER { PROC | PROCEDURE } [schema_name.] procedure_name
[ { @parameterdata_type } [= ] ] [ ,…n ] AS { [ BEGIN ] sql_statement [ ; ] [ ,…n ] [ END ] } [;]
具体操作为:只需将创建存储过程时的create
改为alter
即可,改动需要改动的地方,其他不变。
drop procedure 过程名;
例:删除刚刚建立的存储过程
drop procedure Proc_TRANSFER;
同:都是持久性存储模块
异:函数必须指定返回的类型下面是标准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标准参考
例:求男女学生的平均年龄 ①定义函数create function AvgAge(@Sex varchar(10)='男') --在这里是给参数默认值,即可以不传参数调用这个函数returns decimalasbegin declare @AvgAge decimal(10,2) select @AvgAge=AVG(Convert(decimal(10,2),Sage)) from Student where Ssex=@Sex return @AvgAgeend;
②测试使用
-- select [函数的所有者].函数名 as 字段别名 select dbo.AvgAge(default) as AvgAge;select dbo.AvgAge('男');select dbo.AvgAge('女');
转载地址:http://flyk.baihongyu.com/