第4章 SQL Server Transact-SQL编程

4.1. 程序中处理错误
4.1.1. RAISERROR
生成错误消息并启动会话的错误处理。RAISERROR 可以引用 sys.messages 目录视图中存储的用户定义消息,也可以动态建立消息。该消息作为服务器错误消息返回到调用应用程序,或返回到 TRY…CATCH 构造的关联 CATCH 块。
语法

 msg_id
使用 sp_addmessage 存储在 sys.messages 目录视图中的用户定义错误消息号。用户定义错误消息的错误号应当大于 50000。如果未指定 msg_id,则 RAISERROR 引发一个错误号为 50000 的错误消息。
 msg_str
用户定义消息,格式与 C 标准库中的 printf 函数类似。该错误消息最长可以有 2,047 个字符。如果该消息包含的字符数等于或超过 2,048 个,则只能显示前 2,044 个并添加一个省略号以表示该消息已被截断。请注意,由于内部存储行为的缘故,代替参数使用的字符数比输出所显示的字符数要多。例如,赋值为 2 的代替参数 %d 实际在消息字符串中生成一个字符,但是还会在内部占用另外三个存储字符串。此存储要求减少了可用于消息输出的字符数。
当指定 msg_str 时,RAISERROR 将引发一个错误号为 5000 的错误消息。
msg_str 是一个字符串,具有可选的嵌入转换规格。每个转换规格都会定义参数列表中的值如何格式化并将其置于 msg_str 中转换规格位置上的字段中。转换规格的格式如下:
% [[flag] [width] [. precision] [{h | l}]] type
可在 msg_str 中使用的参数包括:
 flag
用于确定被替换值的间距和对齐的代码。具体的含义如下表所示,
表 4 1 RAISERROR函数的msg_str参数中flag的含义
代码 前缀或对齐 说明
-(减号) 左对齐 在给定字段宽度内左对齐参数值。
+(加号) 符号前缀 如果参数值为有符号类型,则在参数值的前面加上加号(+)或减号(-)。
0(零) 零填充 在达到最小宽度之前在输出前面加上零。如果出现 0 和减号 (-),将忽略 0。

(数字) 对 x 或 X 的十六进制类型使用 0x 前缀 当使用 o、x 或 X 格式时,数字符号 (#) 标志在任何非零值的前面分别加上 0、0x 或 0X。当 d、i 或 u 的前面有数字符号 (#) 标志时,将忽略该标志。

’ ‘(空格) 空格填充 如果输出值有符号且为正,则在该值前加空格。如果包含在加号(+)标志中,则忽略该标志。
 width
定义放置参数值的字段的最小宽度的整数。如果参数值的长度等于或大于 width,则打印该值,无需进行填充。如果该值小于 width,则将该值填充到 width 中指定的长度。
星号 (*) 表示宽度由参数列表中的相关参数指定,该宽度必须为整数值。
 precision
从字符串值的参数值中得到的最大字符数。例如,如果一个字符串具有五个字符并且精度为 3,则只使用字符串值的前三个字符。

对于整数值,precision 是指打印的最小位数。
星号 (*) 表示精度由参数列表中的相关参数指定,该精度必须为整数值。
{h | l} type
与字符类型 d、i、o、x、X 或 u 一起使用,用于创建 shortint (h) 值或 longint (l) 值。
类型规范 表示
d 或 i 有符号整数
o 无符号八进制数
s 字符串
u 无符号整数
x 或 X 无符号十六进制数

 @local_variable
是一个可以为任何有效字符数据类型的变量,其中包含的字符串的格式化方式与 msg_str 相同。@local_variable 必须为 char 或 varchar,或者能够隐式转换为这些数据类型。
 severity
用户定义的与该消息关联的严重级别。当使用 msg_id 引发使用 sp_addmessage 创建的用户定义消息时,RAISERROR 上指定的严重性将覆盖 sp_addmessage 中指定的严重性。
任何用户都可以指定 0 到 18 之间的严重级别。只有 sysadmin 固定服务器角色成员或具有 ALTER TRACE 权限的用户才能指定 19 到 25 之间的严重级别。若要使用 19 到 25 之间的严重级别,必须选择 WITH LOG 选项。
20 到 25 之间的严重级别被认为是致命的。如果遇到致命的严重级别,客户端连接将在收到消息后终止,并将错误记录到错误日志和应用程序日志。
小于 0 的严重级别被解释为级别为 0。大于 25 的严重级别被解释为级别为 25。
 state
介于 1 至 127 之间的任意整数。state 的负值默认为 1。值为 0 或大于 127 会生成错误。

如果在多个位置引发相同的用户定义错误,则针对每个位置使用唯一的状态号有助于找到引发错误的代码段。
 argument
用于代替 msg_str 或对应于 msg_id 的消息中的定义的变量的参数。可以有 0 个或多个代替参数,但是代替参数的总数不能超过 20 个。每个代替参数都可以是局部变量或具有下列任一数据类型:tinyint、smallint、int、char、varchar、nchar、nvarchar、binary 或 varbinary。不支持其他数据类型。
 option
错误的自定义选项,可以是下表中的任一值。
表 4 2 RAISERROR函数的参数option的值
值 说明
LOG 在 Microsoft SQL Server 数据库引擎实例的错误日志和应用程序日志中记录错误。记录到错误日志的错误目前被限定为最多 440 字节。只有 sysadmin 固定服务器角色成员或具有 ALTER TRACE 权限的用户才能指定 WITH LOG。
NOWAIT 将消息立即发送给客户端。
SETERROR 将 @@ERROR 值和 ERROR_NUMBER 值设置为 msg_id 或 50000,不用考虑严重级别。

RAISERROR 生成的错误与数据库引擎代码生成的错误的运行方式相同。RAISERROR 指定的值由 ERROR_LINE、ERROR_MESSAGE、ERROR_NUMBER、ERROR_PROCEDURE、ERROR_SEVERITY、ERROR_STATE 以及 @@ERROR 等系统函数来报告。当 RAISERROR 在严重级别为 11 或更高的情况下在 TRY 块中运行,它便会将控制传输至关联的 CATCH 块。如果 RAISERROR 在下列情况下运行,便会将错误返回到调用方:
 在任何 TRY 块的作用域之外运行。
 在严重级别为 10 或更低的情况下在 TRY 块中运行。
 在严重级别为 20 或更高的情况下终止数据库连接。
CATCH 块可以使用 RAISERROR 来再次引发调用 CATCH 块的错误,方法是使用 ERROR_NUMBER 和 ERROR_MESSAGE 之类的系统函数检索原始错误消息。对于严重级别为 1 到 10 的消息,@@ERROR 默认值为 0。
当 msg_id 指定 sys.messages 目录视图中可用的用户定义消息时,RAISERROR 按照与应用到使用 msg_str 指定的用户定义消息文本的规则相同的规则处理文本列中的消息。用户定义消息文本可以包含转换规格,并且 RAISERROR 将参数值映射到转换规格。使用 sp_addmessage 添加用户定义错误消息,而使用 sp_dropmessage 删除用户定义错误消息。
RAISERROR 可以代替 PRINT 将消息返回到调用应用程序。RAISERROR 支持类似于 C 标准库中 printf 函数功能的字符代替,而 Transact-SQL PRINT 语句则不支持。PRINT 语句不受 TRY 块的影响,而在严重级别为 11 到 19 的情况下在 TRY 块中运行的 RAISERROR 会将控制传输至关联的 CATCH 块。指定严重级别为 10 或更低以使用 RAISERROR 返回 TRY 块中的消息,而不必调用 CATCH 块。
通常,连续的参数替换连续的转换规格;第一个参数替换第一个转换规格,第二个参数替换第二个转换规格,以此类推。例如,在以下 RAISERROR 语句中,第一个参数 N’number’ 替换第一个转换规格 %s,第二个参数 5 替换第二个转换规格 %d.

如果为转换规格的宽度或精度指定了星号 (*),则要用于宽度或精度的值被指定为整数参数值。在这种情况下,一个转换规格最多可以使用三个参数,分别用作宽度、精度和代替值。
例如,下列两个 RAISERROR 语句都返回相同的字符串。一个指定参数列表中的宽度值和精度值;另一个指定转换规格中的宽度值和精度值。

RAISEERROR示例:从 CATCH 块返回错误消息
以下代码示例显示如何在 TRY 块中使用 RAISERROR 使执行跳至关联的 CATCH 块中。它还显示如何使用 RAISERROR 返回有关调用 CATCH 块的错误的信息。

RAISEERROR示例:在 sys.messages 中创建即席消息
以下示例显示如何引发 sys.messages 目录视图中存储的消息。该消息通过 sp_addmessage 系统存储过程,以消息号 50005 添加到 sys.messages 目录视图中。

RAISEERROR示例:使用局部变量提供消息文本
以下代码示例显示如何使用局部变量为 RAISERROR 语句提供消息文本。

4.2. 游标
4.2.1. 游标的基本概念与操作
下面我们通过图 4 1中的程序介绍游标的基本概念与操作。
图 4 1 游标的基本操作

该示例是一个简单的游标操作示例,在示例中声明了一个游标contact_cusor,然后打开游标并从其中提取记录,最后关闭并删除游标。程序中并没有对从游标中提取的记录进行处理,其执行结果如图 4 2所示(只显示部分结果)。

图 4 2 游标基本操作的执行结果

游标的基本操作有四个,包括:声明、打开、提取与关闭(具体的语法请参考SQL Server联机文档中的Transact-SQL语言参考)。其中,
每个游标的第一个操作总是声明。声明游标就像其它编程语言中声明变量一样,声明的变量必须有数据类型,游标的数据类型就是游标,但不同的是声明游标还需要有一个SELECT查询语句,查询语句中不能够使用变量。
游标的第二个操作是打开(程序中的第(2)步)。打开游标实际上是在服务器上执行游标声明中的SELECT语句,游标打开后游标的值就是查询语句的结果。实际上,游标是T-SQL中的一种特殊的数据类型,这种数据类型可以存储一个行集或记录集。在执行打开操作后,游标的行集会保存在临时数据库中,就像一个临时表一样,但只能够通过游标操作该记录集。
游标的打开操作就像是变量的初始化。当然,变量在初始化后还可以改变其值,但没有相应的操作能够直接修改游标的行集。但这并不是说游标的内容在打开后就不发生变化了,如果声明的游标不是静态游标,则其内容会随着SELECT语句中表的记录的变化而变化。
游标打开后的操作通常是FETCH提取操作(程序中的第(3)、(4)步)。提取操作每次从游标中提取一行。提取出来的一行可以放到变量中以进行相应的处理,但该程序中并没有对提取的行进行任何操作。
游标有一个当前行,它有一个指针指向当前行,该指针就像一个“游标”一样,这也是术语“游标”的由来。打开后当前行是第一行,程序中第一个FETCH NEXT语句即是提取当前行并把指针移到下一行。可以有其它的方式提取。
提取操作并不一定成功,如果记录集中没有记录或者游标当前行是最后一行的后面,则提取操作不成功。可以使用系统变量@@FETCH_STATUS检查前面的FETCH操作是否成功,如果成功则该系统变量的值为0。程序中使用一个WHILE循环来提取其它所有的行,每次提取后都通过判断系统变量@@FETCH_STATUS的值是否为0以确定是否进行下一个行的提取。通常,在程序中我们都会采用这种结构来提取游标中所有的行。
游标使用完毕需要关闭(程序中的CLOSE contact_cursor语句)。游标一旦关闭便不能对其进行提取操作,必须再次打开后才可以进行提取。游标的关闭就像C或C++语言中对一个分配内存的指针进行delete操作一样。
游标关闭后并不是说游标不存在了,游标在程序中仍然存在,不能声明一个相同名称的游标。只有在执行游标的删除操作(程序中的DEALLOCATE contact_cursor语句)才删除了游标,与该游标有关的资源也同时被释放。应注意,不能对一个打开的游标进行删除操作。
另外,许多程序中没有删除操作甚至没有关闭操作,但程序也不会出现错误。这与C或C++程序中给指针分配的内存但没有释放是一样的道理,如果程序执行结束,服务器会自动释放相应的资源。
4.2.2. 处理游标中的行
通常,从游标中提取的行需要作进一步的处理,如使用其中的值进行判断、计算或更新数据库。因此,简单的FETCH操作并不能完成这一功能。下面的示例演示了上一节中的游标示例,所不同的是每次提取的行的值被存储到两个变量@LastName和@FirstName,利用这两个变量生成一个包含姓名的字符串。
程序中第(1)步是声明两个变量@LastName和@FirstName。程序中声明的变量必须以@符号开头。这两个变量用于保存从游标中提取的行的列值。
第(2)步是声明一个游标contact_cursor,这与上一节所介绍的相同。
第(3)步是打开游标。
图 4 3 提取游标的行并进行处理示例

第(4)步是第一次提取游标,语句中的INTO @LastName, @FirstName子句表明将提取的行的列值分别存储在变量@LastName与@FirstName中。需要注意的是,INTO子句中的变量的个数与数据类型必须与游标的SELECT语句的输出结果的列数和数据类型相一致。数据类型可以不完全相同,但必须保证变量的数据类型可以存储结果中相应的列值。
第(5)步检查系统变量@@FETCH_STATUS的值以判断前面的提取是否成功,如果成功则进行第(6)步。
第(6)步使用PRINT函数输出连接的字符串。这里的处理虽然简单,但与复杂处理的基本原理是一样的。
第(7)是进行下一个提取。
第(8)步是关闭并删除游标。
执行结果如图 4 4所示(由于结果中的行太多,只显示部分结果):

图 4 4提取游标的行并进行处理示例的结果

4.3. 存储过程

4.4. 触发器
触发器是数据库服务器中发生事件时自动执行的特种存储过程。SQL Server支持DML、DDL与登录触发器,我们这里只演示DML触发器。如果用户要通过数据操作语言 (DML) 事件编辑数据,则执行 DML 触发器。DML 事件是针对表或视图的 INSERT、UPDATE 或 DELETE 语句。
DML 触发器在以下方面非常有用:

(1) DML 触发器可通过数据库中的相关表实现级联更改。不过,通过级联引用完整性约束可以更有效地进行这些更改。
(2) DML 触发器可以防止恶意或错误的 INSERT、UPDATE 以及 DELETE 操作,并强制执行比 CHECK 约束定义的限制更为复杂的其他限制。
(3) 与 CHECK 约束不同,DML 触发器可以引用其他表中的列。例如,触发器可以使用另一个表中的 SELECT 比较插入或更新的数据,以及执行其他操作,如修改数据或显示用户定义错误信息。
(4) DML 触发器可以评估数据修改前后表的状态,并根据该差异采取措施。
(5) 一个表中的多个同类 DML 触发器(INSERT、UPDATE 或 DELETE)允许采取多个不同的操作来响应同一个修改语句。
4.4.1. DML触发器的类型
(1) AFTER 触发器
在执行了 INSERT、UPDATE 或 DELETE 语句操作之后执行 AFTER 触发器。指定 AFTER 与指定 FOR 相同,它是 Microsoft SQL Server 早期版本中唯一可用的选项。AFTER 触发器只能在表上指定。
(2) INSTEAD OF 触发器
执行 INSTEAD OF 触发器代替通常的触发动作。还可为带有一个或多个基表的视图定义 INSTEAD OF 触发器,而这些触发器能够扩展视图可支持的更新类型。
(3) CLR 触发器
CLR 触发器可以是 AFTER 触发器或 INSTEAD OF 触发器。CLR 触发器还可以是 DDL 触发器。CLR 触发器将执行在托管代码(在 .NET Framework 中创建并在 SQL Server 中上载的程序集的成员)中编写的方法,而不用执行 Transact-SQL 存储过程。有关详细信息,请参阅编程 CLR 触发器。
4.4.2. AFTER 触发器与INSTEAD OF 触发器的比较
(1) 执行 INSTEAD OF 触发器代替通常的触发操作。还可以对带有一个或多个基表的视图定义 INSTEAD OF 触发器,这些触发器可以扩展视图可支持的更新类型。
(2) 在执行 INSERT、UPDATE 或 DELETE 语句操作之后执行 AFTER 触发器。指定 AFTER 与指定 FOR 相同。AFTER 触发器只能在表上指定。
下表对 AFTER 触发器和 INSTEAD OF 触发器的功能进行了比较。
需要注意的是AFTER触发器晚于约束执行,而INSTEAD OF 触发器是先于约束执行。比如当插入一个记录到某一子表中时,对于AFTER触发器,会先检查插入的记录是否违反了引用约束条件,即插入的外码列的值是否是父表中主码列的值,而对于INSTEAD OF触发器则会先执行触发器。
表 4 3 AFTER与INSTEAD OF触发器的比较
函数 AFTER 触发器 INSTEAD OF 触发器
适用范围 表 表和视图
每个表或视图包含触发器的数量 每个触发操作(UPDATE、DELETE 和 INSERT)包含多个触发器 每个触发操作(UPDATE、DELETE 和 INSERT)包含一个触发器
级联引用 无任何限制条件 不允许在作为级联引用完整性约束目标的表上使用 INSTEAD OF UPDATE 和 DELETE 触发器。
执行 晚于:
• 约束处理
• 声明性引用操作
• 创建插入的和删除的表
• 触发操作 早于:
• 约束处理
替代:
• 触发操作
晚于:
• 创建插入的和删除的表
4.4.3. AFTER触发器示例
创建触发器使用CREATE TRIGGER命令。图 4 5中的触发器示例创建了一个名为LowCredit的触发器,该触发器的触发事件为表Purchasing.PurchaseOrderHeader的INSERT操作,即每当插入一条记录到表中后就会自动执行触发器的程序。
图 4 5 触发器示例
示例的基本功能是当插入订单记录到订单基本信息表Purchasing.PurchaseOrderHeader中时,根据插入订单的厂商编号从从厂商基本信息表Purchasing.Vendor中查询厂商的信用等级(Vender.CreditRating列)并将得到的信用等级的值存储到变量@creditrating中。如果厂商的信用等级等于5,则程序使用系统函数RAISEERROR抛出一个错误信息,错误的严重级别为16,RAISEERROR函数中的第3个参数1表示状态,可以使用它定位程序出错的位置。如果信用等级太低,程序使用ROLLBACK TRANSACTION语句撤消当前事务。
测试触发器前先查询厂商基本信息表,使用如下的命令:
图 4 6 查询厂商基本信息表的命令
查询结果如下(只显示一部分)

图 4 7 厂商基本信息表的查询结果
在下面的测试中我们将使用厂商编号为22(信用等级为5)、厂商编号为93(信用等级为2)的两个记录。
使用下面的命令检查订单基本信息表中的订单个数。
图 4 8 查询订单数量的命令
查询的结果如下:

图 4 9 执行插入新订单前的订单数量
我们使用下面的命令插入一个新订单,其中,订单的编号由系统自动生成,员工的编号为198,厂商的编号为22,订单的日期使用系统当前的日期(由系统函数get_date返回),订单的发货类型编号为5。由于这里只是测试触发器,所以我们并不关心插入数据的合理性,只要符合订单基本信息表的约束条件即可。
图 4 10 插入厂商编号为22的新订单
在成功创建触发器后,执行上面的插入操作命令,显示如下的结果:
图 4 11 插入厂商编号为22的新订单的执行结果
为了验证新订单是否插入到表中,我们仍然使用图 4 8中的命令查询表中订单的数量,验证的结果与图 4 9中的所显示的数量相同,说明新订单没有插入到订单基本信息表中。
接下来我们使用下面的命令插入一个另一个新订单,该订单中厂商的编号为93,从图 4 7所示的厂商基本信息中可知该厂商的信用等级为2,符合触发器规定的厂商信用等级要求。
图 4 12 插入厂商编号为93的新订单
执行结果如下:
图 4 13 插入厂商编号为93的新订单的结果
说明新订单插入成功。我们仍然可以使用图 4 8中所示的命令查询表中订单的数量,查询的结果如所示:
图 4 14 插入厂商编号为93的新订单后的订单数量
4.4.4. 允许与禁止触发器
可以在需要时使用下面的命令禁止某一个触发器,
图 4 15 禁止表Purchasing.PurchaseOrderHeader的触发器LowCredit
执行该命令则禁止表前面创建的订单基本信息表的触发器LowCredit,我们仍然使用图 4 10中的命令插入新的订单,执行的结果显示新的订单插入成功。此时,说明并没有执行触发器LowCredit。
可以使用下面的命令允许触发器:
图 4 16 允许表Purchasing.PurchaseOrderHeader的触发器LowCredit
4.4.5. 删除的表deleted和插入的表inserted
DML 触发器语句使用两种特殊的表:删除的表deleted和插入的表inserted。SQL Server会自动创建和管理这两种表。可以使用这两种驻留内存的临时表来测试特定数据修改的影响以及设置 DML 触发器操作条件。但不能直接修改表中的数据或对表执行数据定义语言 (DDL) 操作。
在 DML 触发器中,inserted 和 deleted 表主要用于执行以下操作:
(1) 扩展表之间的引用完整性。
(2) 在以视图为基础的基表中插入或更新数据。
(3) 检查错误并采取相应的措施。
(4) 找出数据修改前后表的状态差异并基于该差异采取相应的措施。
(5) 删除的表用于存储 DELETE 和 UPDATE 语句所影响的行的副本。在执行 DELETE 或 UPDATE 语句的过程中,行从触发器表中删除,并传输到删除的表中。删除的表和触发器表通常没有相同的行。
(6) 插入的表用于存储 INSERT 和 UPDATE 语句所影响的行的副本。在执行插入或更新事务过程中,新行会同时添加到 inserted 表和触发器表中。插入的表中的行是触发器表中的新行的副本。
(7) 更新事务类似于在删除操作之后执行插入操作;首先,旧行被复制到删除的表中,然后,新行被复制到触发器表和插入的表中。
(8) 在设置触发器条件时,应使用激发触发器的操作相应的插入的和删除的表。尽管在测试 INSERT 时引用删除的表或在测试 DELETE 时引用插入的表不会导致任何错误,但在这些情况下,这些触发器测试表将不包含任何行。
图 4 5触发器示例中的插入记录即存储在插入表inserted中,查询时首先从inserted表中得到插入记录(插入的新订单)的厂商的编号inserted.VendorID,然后使用插入记录中的厂商编号到厂商基本信息表Purchasing.Vendor中查询得到该厂商的信用等级Vendor.CreditRating。
4.4.6. INSTEAD OF触发器示例一
Transact-SQL 语句创建个人信息表Person和员工信息表EmployeeTable、一个视图Employee、一个记录错误表PersonDuplicates和视图上的 INSTEAD OF 触发器IO_Trig_INS_Employee。
具体的步骤如下:
(1) 首先创建一个数据库Student用于存储所创建的表、视图和触发器。设置数据库Student为当前数据库:
图 4 17 设置Student为当前数据库
(2) 使用下面的命令创建个人信息表Person:
图 4 18 创建个人信息表Person
表Person中的列SSN是个人的社会安全号码,它是该表的主码。
(3) 创建员工信息表EmployeeTable,命令如下:
图 4 19 创建员工信息表EmployeeTable
员工信息表EmployeeTable是个人信息表Person的子表,它的SSN列引用了Person表的SSN列。
(4) 创建视图Employee,命令如下:
图 4 20 创建视图Employee
(5) 创建错误记录表PersonDuplicates,命令如下:
图 4 21 创建错误记录表PersonDuplicates
该表中的SSN列记录社会安全号码。

(6) 创建触发器,命令如下:
图 4 22 创建INSTEAD OF触发器IO_Trig_INS_Employee

(7) 插入正确记录并查询结果。下面插入的2条记录均符合要求,
图 4 23 插入正确的记录
查询表Person,结果如下,
图 4 24 插入正确记录后Person表的记录
查询表EmployeeTable,结果如下,
图 4 25 插入正确记录后的EmployeeTable表的记录
查询表PersonDuplicates,结果如下,
图 4 26 插入正确记录后表PersonDuplicates
(8) 插入错误记录。使用下面的命令插入一条记录
图 4 27 插入有相同SSN的记录
查询表Person与EmployeeTable结果与执行插入前相同,说明记录没有插入到这两个表中,但PersonDuplicates表的内容如下:
图 4 28 插入相同SSN后的PersonDuplicates表
该触发器还有需要验证的内容,请你自行验证。
4.5. INSTEAD OF触发器示例二
下面是示例数据库AdventureWorks中的Purchasing.Vendor表中的一个INSTEAD OF触发器示例,它禁止删除Vender表的记录。
下面是触发器有关的说明。创建触发器的命令选项如NOT FOR REPLICATION可以在联机文档的CREATE TRIGGER命令中找到,@@系统函数可以搜索联机文档的“系统函数”。
 NOT FOR REPLICATION
指示当复制代理修改涉及到触发器的表时,不应执行触发器。
 @@ROWCOUNT
系统函数@@ROWCOUNT返回受上一语句影响的行数。
 @@TRANCOUNT
返回在当前连接上执行的 BEGIN TRANSACTION 语句的数目。
 SET NOCOUNT ON
阻止在结果集中返回可显示受 Transact-SQL 语句或存储过程影响的行计数的消息。如果选项则ON则不显示类似“5行受影响”的消息,只显示“命令已成功完成”,关闭时(即设置为ON,默认为OFF)可提高性能。
 dbo.uspPrintError存储过程
该存储过程打印错误信息,你可以在AdventureWorks示例数据库的“可编程性/存储过程”节点找到其源代码,如下:
 dbo.uspLogError存储过程
dbo.uspLogError存储过程将错误信息写到ErrorLog表中,你可以在示例数据库的“可编程性/存储过程”节点找到其源代码。
 测试
测试的命令:
测试的结果:

注释:RAISEERROR抛出的错误级别不同会导致不同的测试结果。只有错误级别大于10时才会导致执行跳转到CATCH块。
思考题:INSTEAD OF触发器需要撤消触发事件的操作吗?上面触发器中的撤消事务的操作ROLLBACK TRANSACTION是撤消delete操作吗?如果不是,是撤消什么样的操作?举例说明。
4.6. 实验
通过该实验加深存储过程和触发器的基本概念的理解,掌握基本存储过程、触发器的管理和执行,能够使用游标编写较复杂的存储过程,为后面的系统开发打好基础。
4.6.1. 实验一:存储过程创建与使用
创建一个存储过程,执行该存储过程完成如下功能:
查询示例数据库AdventureWorks中的订单基本信息表Sales.SalesOrderHeader、客房基本信息表Sales.Customer和订单明细表Sales.SalesOrderDetail,输出订单编号(SalesOrderID)、订单日期(OrderDate)、发货日期(ShipDate)、客户名称(CustomerID为客户编号)和订单的总价,并将输出结果写到一个新建的表中。
其中,订单的总价是由订单明细表中的各项产品的单价(UnitPrice)相加得到的,即每个订单的总价=SUM(产品数量(OderQty)*产品单价(UnitPrice))。
存储过程需要创建输出结果的表,如果输出结果表已经存在则需要在创建之前将其删除。

4.6.2. 实验二:触发器的创建与测试
功能:
检查订单明细表Sales.SalesOrderDetail中的信息,如果修改记录中的产品单价UnitPrice大于产品公开报价(Production.Product.ListPrice),则不能进行修改并抛出错误信息,否则,进行修改并将修改的有关信息写到Production.ProuctUpdateLog表中。
要求:
1. 使用RAISEERROR抛出错误信息。
2. 修改信息记录表Production.ProductUpdateLog的内容:记录编号、订单编号、订单明细编号、产品编号、产品的公开报价、修改前产品的单价、修改后产品的单价、修改者的登录名。使用存储过程完成该功能,并在存储过程中调用该存储过程。
3. 给出触发器和存储过程的源代码和简要的说明(可以在代码中使用注释进行说明)。
4. 设计触发器测试方案并给出测试的命令和结果,必要时可对测试结果进行分析。
4.6.3. 实验三:触发器的创建与测试
功能:
使用触发器实现课程基本信息表Course的自增式主键,即插入记录时,自动生成主键,所生成的主键=表中主键最大值+1。
列名 数据类型 约束 说明
id int 主键 逻辑id
code varchar(32) 非空且唯一 课程编码
name varchar(64) 非空 课程名
credit float 非空 学分

要求:
1. 使用RAISEERROR抛出错误信息。
2. 给出触发器的源代码和简要的说明(可以在代码中使用注释进行说明)。
3. 设计触发器测试方案并给出测试的命令和结果,必要时可对测试结果进行分析。
4. 应用程序生成主键的方式随数据库的变化而有所不同,以SQL为例,简要说明有哪几种生成主键的方式,各有什么优缺点。

猜你喜欢

转载自blog.csdn.net/maguanzhan7939/article/details/77924666
今日推荐