存储过程与触发器
在示例中主要是对一个BooksDB数据库的数据进行处理的,如果有需要的可以看一下创建该数据库的代码
一.存储过程(stored procedure)
- 为了完成一定功能的一组sql语句
- 经过编译后存储到数据库
- 系统存储过程_sp, 拓展存储过程_xp, 用户自定义存储过程_usp
优点(为什么要用存储过程)
①模块化编程
②减少网络的流量
③提高执行速度
④提高数据的安全性
创建语句
create procedure 存储过程名
参数
as
功能
--执行
exec 存储过程名
--调用语句为批处理的第一条语句时,可省略exec
1.不带参数的存储过程:创建一个存储过程,查看所有读者的姓名、可借本数、可借天数和已借书本数。
--1、 不带参数的存储过程:创建一个存储过程,查看所有读者的姓名、可借本数、可借天数和已借书本数。
create procedure usp_selelctReader
as
select rdName,canLendQty,canLendDay,rdBorrowQty
from Reader, ReaderType
where Reader.rdType=ReaderType.rdType
--测试执行
exec usp_selelctReader
执行运行结果:
2.带输入参数和输出参数的存储过程:创建一个存储过程,输入读者的编号,输出该读者的姓名
--2、 带输入参数和输出参数的存储过程:创建一个存储过程,输入读者的编号,输出该读者的姓名;
create procedure usp_getName
@rdID char(9),
@rdNmae varchar(20) output
as
select @rdNmae=rdName from Reader where [email protected]
--测试执行
declare @rdNmae varchar(20)
exec usp_getName'rd2018007',@rdNmae output
print @rdNmae
运行结果:卢小川
3. 带输入参数的存储过程:创建一个存储过程,实现借书功能
--3、 带输入参数的存储过程:创建一个存储过程,实现借书功能;
alter procedure usp_borrow
@rdID char(9),
@bkID char(9),
@DateBorrow datetime,
@DateLendPlan datetime
as
begin tran
print'begin tran'
begin try
--先判断图书状态
declare @bkState int
select @bkState=bkState from Book where [email protected]
if(@bkState!=1)
raiserror('借书失败!!该图书不在馆!',16,1)
else
begin
--判断读者借书是否已满
declare @rdBorrowQty int,@canLendQty int
select @canLendQty=canLendQty,@rdBorrowQty=rdBorrowQty from Reader,ReaderType
where [email protected] and Reader.rdType=ReaderType.rdType;
if(@rdBorrowQty>[email protected])
raiserror('借书失败!!该读者借书已达上限!',15,1)
else
begin
insert into Borrow(rdID,bkID,DateBorrow,DateLendPlan)
values(@rdID,@bkID, @DateBorrow,@DateLendPlan);
--修改图书状态
update Book set bkState=0 where [email protected];
--修改借书数量
update Reader set rdBorrowQty=rdBorrowQty+1 where [email protected];
print @rdID+'借'[email protected]+'成功!!'
commit
print'commit'
end
end
end try
begin catch
DECLARE @ErrorMessage NVARCHAR(4000)
select @ErrorMessage = ERROR_MESSAGE()
raiserror(@ErrorMessage,16,1)
rollback
print'rollback'
end catch
测试代码1:
--测试执行
declare @date datetime,@date_plan datetime
--获取当前时间为借书时间
set @date=GETDATE()
--计划还书时间为借书后的第十天
set @date_plan=GETDATE()+10
exec usp_borrow'rd2018001','bk2018001',@date,@date_plan
运行结果:
再次运行
查询结果:
二.触发器
- 是一特殊的存储过程,通过事件触发而执行的
- 可以实现比primary key,foregin key,check,unique,default更加复杂的数据完整性约束
- DML触发器:insert,delete,update
- DDL触发器:create,alter,drop
两个特殊表(deleted,inserted):系统自动管理,动态驻留内存中
1.DML触发器
为什么使用触发器呢?先来看一个示例代码
--视图学号,姓名,课程号,课程名,成绩
create view view1
as
select Student.Sno,Sname,Course.Cno,Cname,Grade
from Student,SC,Course
where Student.Sno=SC.Sno and Course.Cno=SC.Cno
select * from view1
--结果会提示错误,因为修改会影响多个基表,因此可利用触发器,解决
--视图上只能创建instead of 触发器
create trigger tri_view1 on view1 instead of insert
as
insert into Student(Sno,Sname) select Sno,Sname from inserted
insert into Course(Cno,Cname) select Cno,Cname from inserted
insert into SC(Sno,Cno,Grade) select Sno,Cno,Grade from inserted
2.DDL,DDL事件组
create trigger tri_safe on database
--for create_table,drop_table,alter_table
for ddl_table_events
as
print('数据表上的DDL触发器执行啦')
print('您无法再该石油加油卡上创建表和创建表')
rollback
--测试
create table test_table(
no char(8),
name char(20)
)
select * from test_table
3.服务器触发器
create trigger tri_CheckLogin on all server
for ddl_table_events
as
rollback
print('不能再该数据库上创建,删除,修改登录账号')
create login aa with password='sa123'
--禁用
disable trigger all on all server
4.触发器运用示例
1)创建 DDL 触发器,禁止用户修改 BooksDB 数据库中的表;
实现代码:
创建 DDL 触发器,禁止用户修改 BooksDB 数据库中的表;
create trigger tri_safe on Database
--for create_table,drop_table,alter_table
for ddl_table_events
as
rollback
print('DDL触发器执行啦')
print('您无法在该数据库上修改表')
go
--测试
drop table Borrow
运行结果:
注:go--是批处理结束的语句.批处理:一条或多条SQL语句,一次性发送到SQL sever数据库服务器进行执行,SQL server数据库服务器讲批处理的语句编译成一个可执行单元,称为执行计划,当编译错误的时候不会执行;运行时错误;
2)测试 AFTER 触发器,理解 INSERTED 表和 DELETED 表的作用;
实现代码:
--测试 AFTER 触发器,理解 INSERTED 表和 DELETED 表的作用;
create trigger tri_testAfter on Borrow
for insert,delete
as
select * from inserted
select * from deleted
print'Borrow表上的afetr触发器执行啦!'
--测试
insert into Borrow values('rd2018007','bk2018001',GETDATE(),GETDATE()+10,default)
select * from Borrow
delete Borrow where rdId='rd2018007'
运行结果:
思考题1: 请在SC表上创建一个触发器,当删除SC表中的选课记录时,要求平均成绩表自动更新
实现代码:
--思考:请在SC表上创建一个触发器,当删除SC表中的选课记录时,要求平均成绩表自动更新
--创建平均成绩表
select sno,avg(Grade) avgGrade into avgTable from SC group by sno
--创建触发器
create trigger tri_delSC on SC
for delete
as
print'SC表上的delete触发器执行啦!'
declare @sno char(9)
set @sno=(select Sno from deleted)
update avgTable set avgGrade=(select avg(Grade) from SC where [email protected] group by sno) where [email protected]
print'平均成绩表更新啦'
--测试执行
select * from avgTable
select * from SC
delete SC where sno='2017003' and Cno='2'
--查看数据
select * from avgTable
select * from SC
运行结果:
思考题2: 请在SC表上创建一个触发器,当更新SC表中的选课成绩时,要求平均成绩表自动更新
实现代码:
--思考:请在SC表上创建一个触发器,当更新SC表中的选课成绩时,要求平均成绩表自动更新
select sno,avg(Grade) avgGrade into avgTable from SC group by sno
create trigger tri_updateSC on SC
for update
as
print'SC表上的update触发器执行啦!'
declare @sno char(9)
set @sno=(select Sno from inserted)
update avgTable set avgGrade=(select avg(Grade) from SC where [email protected] group by sno) where [email protected]
print'平均成绩表更新啦'
--测试执行
select * from avgTable
update SC set Grade=99 where sno='2017003' and Cno='1'
--查看数据
select * from avgTable
select * from SC
运行结果:
三.总结:
- 过程可以带输入输出参数,可以不带参数
- 在调用过程时,如果参数是datetime类型的,不能直接写GETDATE(),如果想传入当前时间,可以引用一个变量来存储GETDATE()的值,然后调用时传出参数写变量名
- DDL触发器,可以写具体的操作如: create_table,drop_table,alter_table;也可以直接写事件组: ddl_table_events
- 两个特殊表,deleted,inserted.系统自动管理,动态驻留内存中
- 如果创建了一个视图,该视图的基表有多个,在进行插入数据的时候会提示错误,以为修改会影响多个基表,此时可以利用触发器联合inserted表来对每一个基表进行插入
- 可以创建服务器触发器:on all server;如果禁用触发器使用:disable trigger all on all server
- 在对数据进行插入修改操作时,可以使用事务,进行提交回滚
- 错误提示:raiserror