一、创建与管理存储过程
1.存储过程概述
1.1存储过程的概念
- 一组编译好的、存储在数据库服务器上的和完成特定功能的T-SQL程序。是某数据库的对象。
- 客户端应用程序可以通过调用(指定存储过程的名字并给出参数(如果该存储过程带有参数))来执行存储过程。
1.2使用存储过程的优点
- 存储过程润许标准组件式编程。
- 存储过程能够实现较快的执行速度。
- 能够减少网络流量。
- 可被作为一种安全机制来充分利用。
1.3存储过程的分类
- 系统提供的存储过程——sp_*
- 用户定义存储过程
- T-SQL存储过程:指保存的T-SQL程序,可以接受和返回用户提供的参数。存储过程也可能充数据库向客户端应用程序返回数据。
- CLR存储过程:指对Microsoft.NET Framework 公共语言运行时方法的引用,可以接受和返回用户提供的参数,他们在.NET Framework程序几种是作为类的公共静态方法实现的。
- 扩展存储过程
- 扩展存储过程是以在SQL Server环境之外执行的动态链接库(DLL,Dynamic-Link Libraries)来实现的,通常以前缀xp_开头。扩展存储过程用与存储过程相似的方式来执行。
2.创建存储过程
//语法
CREATE PROC[EDURE] 过程名 --创建存储过程
[[@形参名 数据类型] --输入参数
|[@形参名 数据类型=默认值] --默认值参数
|[@变参名 数据类型 OUTPUT] --输出参数
][,...n]
AS
[BEGIN]
T-SQL语句 --过程体
[END]
//例2(无参):为数据库“Library”创建一个多表查询的存储过程,查询出读者为“程鹏的借阅信息”。
USE Library
GO
CREATE PROCEDURE borrowed_book1
AS
BEGIN
SELECT r.RID,r.Rname,b.BID,k.Bname,b.LendDate
FROM Reader r INNER JOIN Borrow b ON r.RID=b.RID INNER JOIN Book k ON b.BID=k.BID
WHERE Rname='程鹏'
END
//调用
borrowed_book1
或
EXEC borrowed_book1
//例3(值参):为数据库“Library”创建一个查询某读者(姓名在执行存储过程时给出)借阅情况的存储过程。
USE Library
GO
CREATE PROCEDURE borrowed_book2
@name char(8)
AS
BEGIN
SELECT r.RID,r.Rname,b.BID,k.Bname,b.LendDate
FROM Reader r INNER JOIN Borrow b ON r.RID=b.RID INNER JOIN Book k ON b.BID=k.BID
WHERE Rname=@name
END
//调用
EXEC borrowed_book2 '杨淑华'
或
DECLARE @temp1 char(8)
SET @temp1='杨淑华'
EXEC borrowed_book2 @temp1
//例4(默认值参):为数据库“Library”创建一个查询某读者(姓名在执行存储过程时给出)借阅情况的存储过程。
USE Library
GO
CREATE PROCEDURE borrowed_book3
@name char(8)=NULL
AS
BEGIN
IF @name IS NULL
SELECT r.RID,r.Rname,b.BID,k.Bname,b.LendDate
FROM Reader r INNER JOIN Borrow b ON r.RID=b.RID INNER JOIN Book k ON b.BID=k.BID
ELSE
SELECT r.RID,r.Rname,b.BID,k.Bname,b.LendDate
FROM Reader r INNER JOIN Borrow b ON r.RID=b.RID INNER JOIN Book k ON b.BID=k.BID
WHERE Rname=@name
END
//调用
EXEC borrowed_book3
//例5(输出参数):为数据库“Library”创建一个查询某出版社图书总价值和平均价值的存储过程。
USE Library
GO
CREATE PROCEDURE book_price
@Publisher varchar(30),
@SUMPrice decimal(9,2) OUTPUT,
@AVGPrice decimal(9,2) OUTPUT
AS
BEGIN
SELECT @SUMPrice =SUM(price)
FROM Book
WHERE Publisher=@Publisher
SELECT @AVGPrice=AVG(price)
FROM Book
WHERE Publisher=@Publisher
END
//调用
DECLARE @ch varchar(30),@ou1 decimal(9,2),@ou2 decimal(9,2)
SET @ch='人民邮电出版社'
EXEC book_price @ch,@ou1 Output,@ou2 Output
SELECT @ch AS 书名,@ou1 AS 总价值,@ou2 AS 平均价值
3.管理存储过程
//修改
ALTER PROC[EDURE] 过程名 --修改存储过程
[[@形参名 数据类型] --输入参数
|[@形参名 数据类型=默认值] --默认值参数
|[@变参名 数据类型 OUTPUT]--输出参数
][,...n]
AS
[BEGIN]
T-SQL语句 --过程主体
[END]
//删除
DROP PROCEDURE 存储过程名[,...n]
二、创建与管理触发器
1.触发器概述
1.1 触发器的概念
- 特殊的存储过程,是基于表/视图/服务器/数据库创建的。
- 触发器里也包含一系列的T-SQL语句,但它的执行不是用EXEC主动调用的,而是在满足一定条件下自动执行的。
- 当触发器所保护的数据库中的数据经过操作发生变化或者当服务器、数据库中发生数据定义时间时,系统将自动运行触发器中的程序以保证数据库的完整性、正确性和安全性。
- 通俗的讲,触发器是基于一个基表/视图/服务器/数据库,并通过一个事件被调用的存储过程。
2.2触发器的分类
- DML触发器
- 数据操作语言(DML)事件时启用
- INSERT、UPDATE、DELETE语句触发
- 作用
- 系统将触发器和触发它的语句作为可在触发器内回滚的单个事务对待,如果检测到错误(例如,多表操作不一致、不符合事务管理的规定和磁盘空间不足等),则整个事务自动回滚。其主要作用是实现较为复杂的数据完整性控制。
- DDL触发器
- 数据定义语言(DDL)事件时启用
- CREATE、ALTER、DROP语句触发
- 作用
- 用于管理任务,例如审核和控制数据库操作。
- 登录触发器
- LOGIN事件触发
- 作用
- 用于控制数据库服务器的安全,例如拒绝某登录名启动的SQL Server登陆尝试。
2.创建DML触发器
//语法
CREATE TRIGGER <触发器名>
ON <表名|视图名>
{FOR|AFTER|INSTEAD OF}
[UPDATE][,][INSERT][,][DELETE]
AS
[BEGIN]
T-SQL语句
[END]
参数说明:
- FOR|AFTER:仅在触发T-SQL语句中指定的INSERT/UPDATE/DELETE语句操作都成功指定之后才被执行。所有的引用级联操作和约束检查也必须在执行此触发器之前完成。如果仅指定FOR关键字,则AFTER为默认值。不能对视图定义AFTER触发器。
- INSTEAD OF:为表和视图指定的DML触发器用于“替代”引起触发器执行的T-SQL语句,因此其优先级高于触发语句的操作。在表或视图上,每个INSERT、UPDATE和DELETE语句最多定义一个INSTEAD OF 触发器。
临时表inserted和deleted说明:
- DML触发器执行时自动创建两个临时表inserted表和deleted表,触发器工作完成后即被删除。
- inserted表用于临时保存被插入或被更新后的数据行副本。在执行INSERT或UPDATE语句时,新的数据行被插入到触发器表中,同时也被插入到inserted表中。可以从inserted表中读取所插入的数据,进一步进行对其他表的操作。也可以判断所插入的数据是否满足完整性规则,如不满足则可以回滚(撤销)此操作。
- deleted表用于临时保存被删除或被更新后的数据行副本。在执行DELETE或UPDATE语句时,从触发器表中删除数据行并传输到deleted表中。可以从deleted表中检查所删除的数据行是否满足删除条件,如不满足则可以回滚(撤销)此操作。
- 执行UPDATE语句更新数据时,类似于在删除之后执行插入;首先被删除的数据行被传输到deleted表中,然后新的数据行被插入到inserted表中。
2.1创建INSERT事件的AFTER触发器
INSERT事件的AFTER触发器是对指定的表执行插入数据行语句INSERT INTO … VALUES事件之后被激发执行的一段程序代码。
例8:读者借书。
- 使用INSERT语句完成借阅表“Borrow”添加借书信息的操作。
- 使用触发器判断所借书是否已经借出并作想用处理。
- 如果尚未借出,则:
①计算该书的应还日期“SReturnDate”为借期加限借天数;
②将读者表“Reader”该读者的借阅数量“Lendum”增加1本;
③将图书表“Book”该书是否借出“LendOut”置为真; - 已经借出,则:
①提示此书已经借出;
②所添加的借书信息撤销;
//代码
CREATE TRIGGER T_Borrow
ON Borrow
AFTER INSERT AS
BEGIN
DECLARE @dzbh @char(10),@tsbh char(15),@dzlx int,@xjts int
SET @dzbh=(SELECT RID FROM inserted) --从添加行副本inserted中查询出RID并赋值给变量@dzbh
SET @TSBH=(SELECT BID FROM inserted) --从添加行副本inserted中查询出BID并赋值给变量@tsbh
SET @dzlx=(SELECT TypeID FROM Reader WHERE RID=@dzbh) --从表“Reader”中查询出添加行读者的TypeID赋值给变量@dzlx
IF EXISTS(SELECT * FROM Book WHERE BID=@tsbh AND LendOut=0)
BEGIN
UPDATE Borrow --①应还日期为借期加限借天数
SET SReturnDate=DATEADD(dd,(SELECT LimitDays FROM ReaderType WHERE TypeID=@dzlx),LendDate)
WHERE RID=@dzbh AND BID=@tsbh AND RetuenDate IS NULL
UPDATE Reader --②将读者表“Reader”该读者的借阅数量“Lendum”增加1本
SET Lendnum=Lendnum+1
WHERE RID=@dzbh
UPDATE Book --③将图书表“Book”该书是否借出“LendOut”置为真;
SET LendOut=1
WHERE BID=@tsbh
END
ELSE --该书已借出
BEGIN
ROLLBACK --回滚数据行操作
PRINT '该书已借出'
END
END
假设1:
执行以下INSERT语句完成图书借阅的操作。
INSERT INTO Borrow(RID,BID)
VALUES('2000186010','TP321/429')
INSERT语句对借阅表“Borrow”的操作如下
- 读者编号列“RID”得到”2000186010“;
- 图书编号列“BID”得到“TP312/429”;
- 借期列”LendDate”得到定义表时设置的系统默认值GETDATE(),假设系统日期为“2017-11-30”;
- 激活触发器“T_Borrow”,并判断结果为所借书尚未借出;
- 借阅表中的应还日期列“SReturnDate“得到”2018-02-28“在触发器中从表”Reader”中查询出该读者的读者类型(教师),再从表“ReaderType”中查询出该读者类型的限借天数90,由借阅日期“2017-11-30”加90天得到应还日期“2018-02-28”;
- 读者表中读者编号为“200086010”的借阅数量“Lendnum“增加到1;
- 图书表中图书编号为”TP312/429“的图书是否借出列吗”LendOut“置为1(True);
2.2创建UPDATE事件的AFTER触发器
例8:读者还书
- 使用UPDATE语句完成借阅表”Borrow“更新还书信息操作,还期为当前系统日期。
- 使用触发器编程判断还书是否过期及以下处理:
①如果过期,计算过期天数;
②将读者表”Reader“中该读者的借阅数量”Lendnum“减少1本;
③将图书表”Book“该书是否借出”LendOut“置为假;
USE Library
GO
CREATE TRIGGER T_Return
ON Borrow
AFTER UPDATE
AS
BEGIN
DECLARE @days int,@dzbh char(10),@tsbh char(9),@hsrq date
SET @dzbh=(SELECT RID FROM inserted) --从更新行副本inserted中查询出RID并赋值给变量@dzbh
SET @tsbh=(SELECT BID FROM inserted) --从更新行副本inserted中查询出BID并赋值给变量@tsbh
SET @hsrq=(SELECT ReturnDate FROM inserted) ----从更新行副本inserted中查询出RetuenDate并赋值给变量@hsrq
SELECT @days=DATEIFF(day,SReturnDate,ReturnDate)
FROM Borrow
WHERE RID=@dzbh AND BID=@tsbh AND ReturnDate=@hsbh --计算过期天数;
IF @days<=0
PRINT '没有过期!'
ELSE
PRINT '过期'+convert(char(6),@days)+'天'
UPDATE Reader --将读者表”Reader“中该读者的借阅数量”Lendnum“减少1本;
SET Lendnum=Lendnum-1
WHERE RID=@dzbh
UPDATE Book --将图书表”Book“该书是否借出”LendOut“置为假;
SET LendOut=0
WHERE BID=@tsbh
END
2.3创建DELETE事件的AFTER触发器
例10:删除某位读者的信息。
- 若要删除一名读者要先检查该读者是否有书没还,若该读者还有书没还则不能被删除。
USE Library
GO
CREATE TRIGGRT T_ReaderDEL --创建触发器
ON Reader --基于表”Reader“
AFTER DELETE --在删除后触发
AS
BEGIN
DECLARE @LNum int
SELECT @LNum=Lendnum FROM DELETED --从删除的数据行的临时表中获得借阅数量
IF @LNum>0 --如果借阅数量大于0
BEGIN
PRINT '该读者不能删除!还有'+convert(char(2),@LNum)+'本书未还。'
ROLLBACK --事务回滚取消所删除的数据行
END
ELSE --如果借阅数量不大于0
PRINT '该读者已被删除!!!' --显示数据行已删除
END
2.4 创建DELETE事件的INSTEAD OF触发器
例11:删除图书保护。
- 在图书馆图书处理过程中,不允许随意删除表”Book“中的图书。创建表”Book“的INSTEAD OF触发器”T_BookNoDEL“.
USE Library
GO
CREATE TRIGGER T_BookNoDEL --创建INSTEAD触发器
ON Book --基于表Book
INSTEAD OF DELETE --替代触发事件DELETE
AS BEGIN
PRINT '图书未删除!' --显示数据行,图书未被删除
END
3.创建DDL触发器
3.1CREATE、ALTER和DROP触发
语法:
CREATE TRIGGER 触发器名
ON {ALL SERVER|DATABASE}
[WITH ENCRYPTION]
{FOR|AFTER}<事件类型或事件组>[,...n]
AS
[BEGIN]
T-SQL语句
[END]
参数说明:
- ALL SERVER:将DDL触发器的作用域应用于当前服务器。指定此参数,则当前服务器中的任何位置上出现事件类型或事件组,就会激发该触发器。
- DATABASE:将DDL触发器的作用域应用于当前数据库。如果指定了此参数,则只要当前数据库中出现事件类型或事件组,就会激发该触发器。
- WITH ENCRYPTION:对CREATE TRIGGER语句的文本进行加密。
- 事件类型:导致激发DDL触发器的T-SQL语句事件的名称。
- 例如CREATE_TABLE、ALTER_TABLE\DROP_TABLE、CREATE_PROCEDURE等操作。
- 事件组:预定义的T-SQL语句事件分组的名称。执行任何属于事件组的T-SQL语句事件之后,都将激发DDL触发器。
- T-SQL语句:指定触发器所执行的T-SQL语句。
例12:使用DDL触发器来防止数据库”Library“中的任意一个表被修改或删除。
//代码
USE Library
GO
CREATE TRIGGER safety1 ON DATABASE
FOR DROP_TABLE,ALTER_TABLE
AS
BEGIN
PRINT '要删除和修改表之前,你必须先禁用触发器safety1!
ROLLBACK
END
例13:在服务器上创建DDL触发器来防止服务器中的任意一个数据库被修改或删除。
//代码
CREATE TRIGGER safety2 ON ALL SERVER
FOR DROP_DATABASE,ALTER_DATABASE
AS
BEGIN
PRINT '要删除和修改数据库之前,你必须先禁用触发器safety2!'
ROLLBACK
END
4.管理触发器
修改DML触发器。语法如下:
ALTER TRIGGER<触发器名>
ON <表名|视图名>
{FOR|AFTER|INSTEAD OF}
[UPDATE][,][INSERT][,][DELETE]
AS T-SQL语句
修改DDL触发器。语法如下:
ALTER TRIGGER <触发器名>
ON {ALL SERVER|DATABASE}
[WITH ENCRYPTION]
{FOR|AFTER}<事件类型或事件组>[,...n]
AS T-SQL语句
删除触发器。语法如下:
DROP TRIGGER<触发器名>
禁用触发器。语法如下:
DISABLE TRIGGER 触发器名 ON 对象名|DATABASE|ALL SERVER
启用触发器。语法如下:
ENABLE TRIGGER 触发器名 ON 对象名|DATABASE|ALL SERVER
例14:禁用DDL触发器”safety1“,以便进行表的修改和删除。
//代码
DISABLE TRIGGER safety1 ON DATABASE
三、创建与管理用户定义函数
1.用户定义函数的概述
1.1 用户定义函数的概念
用户定义函数
- 一组编译好的、存储在数据库服务器上的和完成特定功能的T-SQL程序,是某数据库的对象。
- 可以将一个或多个T-SQL语句的子程序定义成函数,从而实现代码的封装和重用。
- 用户定义函数(User Defined Function,UDF)可以由多个输入参数并返回标量(常量)或表,不支持输出参数。
用户定义函数的有点 - 允许模块化程序设计。
- 能够实现较快的执行速度。
- 能够减少网络流量。
用户定义函数与存储过程的比较
项目 | 用户定义函数 | 存储过程 |
---|---|---|
参数 | 允许多个输入参数,不允许输出参数 | 允许多个输入/输出参数 |
返回值 | 有且只有一个返回值,可以返回标量或标值 | 可以没有返回值,不能返回表值 |
调用 | 在表达式中引用,可以嵌入在查询语句的表达式中调用 | 必须单独调用 |
2.用户定义函数的分类
标量函数
- 返回的是在RETURNS子句中定义类型的标量表达式的值(单个数据值)。
表值函数:返回的是在RETURNS子句中指定的”TABLE“类型的数据行集(表值)。 - 内联表值函数:没有函数体,RETURN子句在括号中含有一条单独的SELECT查询语句。
- 多语句表值函数:在BEGIN…END语句块中定义的函数体包含一系列T-SQL语句。
2.创建用户定义函数
2.1 创建用户定义标量函数
语法:
CREATE FUNCTION 函数名 --创建标量函数
[({@形参名 数据类型}[,...n])] --括号内输入参数
RETURNS 数据类型 --定义返回标量值的数据类型
[WITH 选项]
AS
BEGIN
T-SQL语句 --函数体
RETURN 标量表达式 --返回RETURNS子句中定义的数据类型的单个数据值
END
例15:创建用户定义标量函数”fn_price“,价格高于50元的书认为是较贵的书,否则认为是便宜的书,实现对图书价格的高与低的评价。
//代码
USE Library
GO
CREATE FUNCTION fn_price(@priceinput money)
RETURNS nvarchar(5) --函数返回nvarchar(5)类型标量
AS
BEGIN
DECLARE @returnstr nvarchar(5)
IF @priceinput>50 --如果输入参数的值大于50
SET @returnstr='娇贵的图书'
ELSE
SET @returnstr='便宜的图书'
RETURN @returnstr --返回字符串标量值
END
2.2 创建用户定义内联表值函数
语法:
CREATE FUNCTION 函数名 --创建标量函数
[({@形参名 数据类型}[,...n])] --括号内输入参数
RETURNS TABLE --定义返回值为表
[WITH 选项]
AS
BEGIN
T-SQL语句 --函数体
RETURN(SELECT查询语句) --返回RETURNS子句中定义的数据类型的单个数据值
END
例16:创建用户定义内联表值函数”fn_Publisher“,根据指定的出版社查询该出版社出版的图书,返回结果数据行集。
//代码
USE Library
GO
CREATE FUNCTION fn_Publisher(@Publisher varhchar(30))
RETURNS TABLE
AS RETURN(SELECT BID,Bname,Author,Publisher
FROM Book WHERE Publisher=@Publisher)
2.3 创建用户定义多语句表值函数
语法:
CREATE FUNCTION 函数名 --创建标量函数
[({@形参名 数据类型}[,...n])] --括号内输入参数
RETURNS @返回变量 TABLE 表类型定义 --定义返回变量为所定义的表类型
[WITH 选项]
AS
BEGIN
T-SQL语句 --函数体(@返回变量得到值)
RETURN(SELECT查询语句) --返回”@返回变量“中存储的数据行集
END
例17:创建用户定义多语句表值函数”fn_Publisher1“,根据指定的出版社参数查询该出版社出版的图书,返回结果数据行集。
//代码
USE Library
GO
CREATE FUNCTION fn_Publisher1
(@Publisher nvarchar(30)) --函数输入参数为varchar(30)类型
RETURNS @tb_Publisher table --返回值为表值
(BID char(13),Bname varchar(42),Author varchar(20),Publisher varchar(30))
AS
BEGIN
INSERT @tb_Publisher --向@tb_Publisher添加数据行
SELECT BID,Bname,Author,Publisher FROM Book
WHERE Publisher=@Publisher
RETURN --返回@tb_Publisher表值
END
3.管理用户定义函数
修改用户定义函数。语法如下:
ALTER FUNCTION 函数名
[({@形参名 数据类型}[,...n])]
RETURNS 返回值数据类型
[WITH 选项]
AS
[BEGIN]
T-SQL语句
RETURN(SELECT查询语句)
[END]
删除用户定义函数。语法如下:
DROP FUNCTION 函数名
举例:删除用户定义函数”fn_Publisher1“
DROP FUNCTION fn_Publisher1