22. 建立及使用触发程序
什么是触发程序?
SQL Server 2000 触发程序强化功能
何时使用触发程序
建立触发程序
管理触发程序
本章总结
触发程序是一种特殊类型的预存程序。本章将学习触发程序的用途和使用方法,以及 Microsoft SQL 2000(T-SQL)触发程序引进的新功能,并将示范两种建立触发程序的方法,分别是使用 T-SQL 陈述式,和使用 SQL Server Enterprise Manager,此外,也将学习管理及修改触发程序。
什么是触发程序?
触发程序(trigger) 是一种特殊的预存程序,执行特定的陈述式(UPDATE、INSERT 或 DELETE)就可以启动触发程序。触发程序与其它预存程序相同,可以是由简单,亦或是复杂的 T-SQL 陈述式组成;至于与其它预存程序不同的地方,则在于当指定的数据被修改,触发程序即自动执行,无法依名称以手动执行。触发程序执行时,称为触动(fire)。触发程序虽建立在现有的数据库数据表中,但它可以存取其它数据库的数据表和对象。触发程序不能建立在临时的数据表或临时的系统数据表上,只能建立在使用者自订数据表或自订的检视表中。执行触发程序所在的数据表或检视表,称为触发程序数据表(trigger table)。
触发程序有五种类型:UPDATE、INSERT、DELETE、INSTEAD OF 和 AFTER。有了触发程序,只要您对该表格更新、插入或删除时,就会触动对应的 UPDATE、INSERT 或 DELETE 触发程序。INSTEAD OF 和 AFTER 是 SQL 2000 新增的两项触发程序,Instead of的原义是「取代」,INSTEAD OF触发程序会取代插入、更新和删除操作而执行。AFTER 触发程序会在触发动作之后再触动,可视为控制触发程序启动时间的机制。
对数据的更新、插入及删除被视为数据修改事件。您可以设计当一项或多项修改事件产生时,即触动触发程序。例如,当执行 UPDATE 或 INSERT 陈述式时即触动触发程序。这种类型的触发程序称为 UPDATE/INSERT 触发程序。您也可以建立任何一项修改事件产生时,执行相对的 UPDATE/INSERT/DELETE 触发程序。
下面是关于触发程序的一些其它规定:
• 触发程序只在触发它的陈述式完成后执行。举例来说,如果 UPDATE 陈述式成功,UPDATE 触发程序才会被触动。
• 如果陈述式在数据表中执行违反条件约束或引起错误,触发程序不会触动。
• 触发程序视为单一交易中的一部份,因此可以由原触发程序复原交易,如果在交易过程中侦测到严重的错误(如使用者中断联机),则会自动复原整个交易。
• 一个陈述式只能触动一次触发程序。
当触发程序触动,若产生任何结果,就会如预存程序一样,将结果传回其呼叫的应用程序。一般来说,INSERT、UPDATE 或 DELETE 的陈述式(触动触发程序的陈述式)不会将结果传回;结果通常由 SELECT 查询传回。因此,为了避免触发程序传回结果给应用程序,请勿在触发过程定义中引入 SELECT 陈述式或指派变数。如果希望从触发程序中传回结果,在允许修改触发数据表的每个应用程序中都必须撰写特殊的程序,才能使应用程序收到传回的数据并进行正确的处理。
如果您必须在触发程序中指派变量,则可在触发程序的起始位置使用 SET NOCOUNT ON 陈述式以防止传回任何结果资料列。SET NOCOUNT 陈述式指定是否传回查询或受陈述式影响的数据列数目的信息(例如,影响 23 个数据列)。SET NOCOUNT 的默认值是设在 OFF,也就是说会传回受影响列的讯息。该设定并不影响 SELECT 陈述式实际结果的传回,只传回计数。
SQL Server 2000 触发程序强化功能
SQL Sever 2000 新增了 INSTEAD OF 及 AFTER 两项触发程序。INSTEAD OF 触发程序可以替代触发的 SQL 叙述而执行,您只能对每个陈述式(INSERT、UPDATE 与 DELETE)定义一个 INSTEAD OF 触发程序。INSTEAD OF 可以定义于一个数据表或是检视表中。若是数个检视表中都有 INSTEAD OF 触发程序,则可以在检视表中藉由定义检视表将 INSTEAD OF 触发程序串联(cascade)。若是在可更新检视表中含有 WITH CHECK 选项,则无法使用 INSTEAD OF 触发程序。若在可更新检视表中定义 INSTEAD OF 触发程序,则必须用 ALTER VIEW 命令将 WITH CHECK 选项从可更新检视表中移除。有关建立检视表的详细信息,请参阅本书 第十八章 。
AFTER 触发程序会在触发 SQL 陈述式中 所有的 动作顺利完成之后才触动,引起参考串联(referential cascade)及条件约束检查(constraint checks)动作。您可以为每个触发动作(INSERT、UPDATE 或 DELETE)指定多个 After 触发程序。如果数据表有多个 After 触发程序的话,可以使用sp_settriggerorder定义哪个 After 触发程序先触动,哪个最后触动。除了头尾两个 After 触发程序外,所有其它触发程序触动的顺序无法由您控制。
除了新增的触发程序外,SQL Server 2000 允许在数据表或检视表中定义触发程序。之前的版本仅允许在数据表中定义触发程序。检视表中的触发程序的功能定义和在数据表中的功能定义相同。
何时使用触发程序
触发程序和条件约束相同,可用来维持数据的完整性和商业规则,但是触发程序不能取代条件约束(条件约束在本书 第十六章 有讨论)。例如,您不需要建立触发程序来检查数据表中主索引键中的某个值是否存在,才能决定这个值是否能被插入到另一个资料表中的相对应数据行(外部索引键条件约束在这种情况下才是一个较好的选择)。您应当建立一个触发程序来启动数据库中所有相关数据表的串联变更,例如,您可能在 pubs 数据库中titles这个数据表中的title_id数据行上建立 DELETE 触发程序,当您在titles字段中删除一笔数据时,在sales、roysched和titleauthor数据表中对应数据的数据行也会被删除(在接下来的章节我们将看到如何建立该 DELETE 触发程序)。
您还可以利用触发程序执行比 CHECK 条件约束更复杂的数据检测(CHECK 条件约束在 第十六章 有详细讨论)。由于触发程序可以引用其它数据表中的数据行,因此才可能执行复杂的数据检测;反之,CHECK 条件约束只限于在其所定义的数据表上执行。
您还可以建立多重触发程序,当数据修改时即触动所有触发程序。(请记住,如果在数据表或检视表中为一个事件定义多重触发程序,每个触发程序都必须有一个自己的名称)。
或者您可以建立单一触发程序,在数据修改时即被触动。也就是每一次当被定义的事件发生,触发程序就被触动一次。因此,若是在数据表上定义 INSERT、UPDATE 和 DELETE 的触发程序,每次定义的事件产生时,触发程序就会触动。
在建立触发程序时,SQL Server 会为触发程序建立两个暂时数据表,您可以参考这两个数据表,用 T-SQL 撰写触发过程定义。这些数据表固定储存在与触发程序一起的内存中,每个触发程序只能存取自己的暂时数据表,暂时数据表即为触发程序所在数据表的一个副本。您可以使用这些数据表比较数据修改前后状态。下一节将列举这些特殊数据表(称为deleted和inserted数据表)。
建立触发程序
认识了触发程序以及用途,现在来了解建立触发程序的细节。首先,我们会用 T-SQL 建立触发程序,然后再学习利用 Enterprise Manager 来建立。利用 Enterprise Manager 建立触发程序,也必须了解 T-SQL 撰写程序,这和利用 Enterprise Manager 建立其它预存程序的方法是一样的。
CREATE TRIGGER 陈述式
要利用 T-SQL 建立触发程序,需要使用 CREATE TRIGGER 这个陈述式,以下为 CREATE TRIGGER 陈述式的基本语法:
CREATE TRIGGERtrigger_name
ON {table | name}
[WITH ENCRYPTION]
{FOR | AFTER | INSTEAD OF}
{[DELETE] [,] [UPDATE] [,]}
[WITH APPEND]
[NOT FOR REPLICATION]
AS
sql_statement[....n]
如您所视,您可以为 INSERT、UPDATE、DELETE、INSTEAD OF 或 AFTER 陈述式建立单一或一组触发程序。如果您使用 FOR 子句,则必须指定至少一个陈述式选项,该子句会指明在数据表内,哪种数据修改事件类型才可引起触发程序的触动。
当触发程序被呼叫时,会执行 AS 关键词后的 SQL 陈述式或陈述式集合,也可包含程序化结构,例如 IF 和 WHILE。以下所列出的陈述式,在触发过程定义中是不允许的:
• ALTER DATABASE
• CREATE DATABASE
• DISK INIT
• DISK RESIZE
• LOAD DATABASE
• LOAD LOG
• RECONFIGURE
• RESTORE DATABASE
• RESTORE LOG
使用deleted和inserted数据表
之前曾提到,当建立触发程序时,您可以存取两个特定的数据表。这两个数据表虽被称为数据表,其实并不同于一般的数据库数据表,它们储存在内存中,而非在磁盘上。这两个资料表被命名为deleted和inserted。
两个数据表的结构类似(相同的数据列及同一类型的数据)于定义触发程序的数据表。deleted数据表会储存因 DELETE 及 UPDATE 陈述式而受影响的数据列副本。当数据列因触发程序被删除或更新时,被删除或更新的数据列会传送到delete数据表;在触发程序中即可使用deleted数据表。inserted数据表会储存被 INSERT 及 UPDATE 陈述式影响的数据列副本,在插入或更新交易时,新的数据列会同时被加至触发程序数据表与inserted数据表。由于执行 UPDATE 陈述式时,会被视为插入或删除交易,旧的数据列值会保留一份副本在deleted数据表中,而新的数据列值的副本则保留在触发程序数据表与inserted数据表。
当执行 INSERT 陈述式时,新增的数据列不会被传送到deleted数据表,当 INSERT 陈述式启动后去检查 deleted 数据表的内容,数据表中虽不会保有任何副本,却不会发生错误讯息。相同的,当执行 DELETE 陈述式时,删除的数据列也不会传到inserted数据表。所以,如欲检视数据修改后的状态,请参考正确的数据表。
________________________________________
说明
inserted和deleted数据表中的值只限于在触发程序中使用。一旦触发程序完成,就无法再使用这两个数据表。
________________________________________
建立第一个触发程序
想要看触发程序如何运作,先建立一个包含触发程序的简单数据表,并定义成当数据更新时,会打印一个陈述式。以下是建立这个数据表的 T-SQL:
USE MyDB
GO
CREATE TABLE Bicycle_Inventory
(
make_name char(10) NOT NULL,
make_id tinyint NOT NULL,
model_name char(12) NOT NULL,
model_id tinyint NOT NULL,
in_stock tinyint NOT NULL,
on_order tinyint NULL,
)
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Print_Update" AND
type = "TR")
DROP TRIGGER Print_Update
GO
CREATE TRIGGER Print_Update
ON Bicycle_Inventory
FOR UPDATE
AS
PRINT "The Bicycle_Inventory table was updated"
GO
为了测试我们的触发程序,加入数据列后更新数据表:
INSERT INTO Bicycle_Inventory VALUES ("Trek", 1, "5500", 5, 1, 0)
GO
UPDATE Bicycle_Inventory
SET make_id = 2
WHERE model_name = "5500"
GO
________________________________________
译注
如果您发现执行上述 SQL 陈述式时发生错误,您可以在建立触发程序之前加上「Set Quoted_Identifier OFF」陈述式以关闭 使用引号识别项功能 。
________________________________________
因为触发程序被 UPDATE 启动,所以传回了「The Bicycle_Inventory table was updated」讯息。在这个范例中,我们将触发过程定义为当触发程序执行后即显示讯息。在某些特定的情况,可以要求触发程序传回输出结果,例如当您建立一个 UPDATE 触发程序,定义为当指定的数据列接收特定的值才触动,但是却担心数据的更新并不正确。此时若是有要求触发程序回传输出结果(在触发程序中加入 PRINT 陈述式),即可从结果中找出可能的错误;否则,一般来说您无须要求触发程序回传输出结果。
建立 DELETE 触发程序
现在来看一个更复杂的范例:利用 DELETE 陈述式,启动数据表的串联更新。我们将建立一个触发程序,当从titles数据表中删除数据列时,在 pubs 数据库内其它的数据表(如sales、roysched和titleauthor),与该数据相关的数据列都会一并删除。我们会利用 deleted 数据表指出哪些数据列该从相关数据表中删除。(当数据列自触发程序数据表中删除时,即在deleted数据表中会有删除数据的副本,所以您可以到deleted资料表检视所有被触发程序所删除的数据列)。要使触发程序运作,我们必须删除titleauthor、roysched和sales数据表中的外部索引键条件约束,因为这些数据表的外部索引条件约束会参考title_id列中的数据,若不删除外部索引键条件约束,触发程序会在删除title_id列时产生错误。
________________________________________
说明
如果您不介意 pubs 数据库被修改,可试着自行删除数据库中的外部索引键条件约束,然后建立触发程序。利用 Enterprise Manager 的数据库图表删除外部索引键条件约束是最简单的方法(在 第十六章 曾经说明)。请确认已删除在其它数据表中,参考title_id列数据的外部索引键条件约束。
________________________________________
本触发程序的 T-SQL 如下所示:
USE pubs
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Delete_Title" AND
type = "TR")
DROP TRIGGER Delete_Title
GO
CREATE TRIGGER Delete_Title
ON titles
FOR DELETE
AS
DELETE sales
FROM sales, deleted
WHERE sales.title_id = deleted.title_id
PRINT "Deleted from sales"
DELETE roysched
FROM roysched, deleted
WHERE roysched.title_id = deleted.title_id
PRINT "Deleted from roysched"
DELETE titleauthor
FROM titleauthor, deleted
WHERE titleauthor.title_id = deleted.title_id
PRINT "Deleted from titleauthor"
GO
要测试触发程序,请使用以下的 DELETE 陈述式:
DELETE titles
WHERE title_id = "PC1035"
GO
假设您已删除稍早提及的外部索引键,一旦执行这个 DELETE 陈述式,触发程序便会触动,您会在title数据表上看到数据修改事件影响列的讯息,接着是从触发程序中三行 PRINT 陈述式说明的讯息,并列出每个数据表中受影响的列数,输出结果如下:
(影响 1 个数据列)
Deleted from sales
(影响 5 个数据列)
Deleted from roysched
(影响 1 个数据列)
Deleted from titleauthor
(影响 1 个数据列)
deleted数据表的另一个用途,是将数据表中被删除的数据列储存到备份数据表中,以供日后的资料分析。例如,您可以利用以下的语法,从roysched数据表将被删除的数据列存入一个命名为roysched_backup的资料表中:
USE pubs
GO
CREATE TABLE roysched_backup
(
title_id tid NOT NULL,
lorange int NULL,
hirange int NULL,
royalty int NULL
)
CREATE TRIGGER tr_roysched_backup
ON roysched
FOR DELETE
AS
INSERT INTO roysched_backup SELECT * FROM deleted
GO
SELECT * FROM roysched_backup
GO
请注意,我们把备份数据表的数据行名称与数据型态,设定成和来源数据表相同。您可以自行命名数据行,但是两个数据表的数据型态应该相同,以确保数据表间的兼容性。
建立 INSERT 触发程序
在这个范例中,我们将建立一个 INSERT 触发程序(当 INSERT 陈述式执行时,会让此触发程序触动),当我们在sales数据表的qty数据列中插入新值,触发程序会更新titles数据表中的ytd_sales数据列。触发程序查询inserted数据表以获得刚刚插入sales数据表的qty值。我们在可在触发程序内引用 SELECT 陈述式以显示inserted数据表中所含的资料。
以下是该触发程序的 T-SQL:
USE pubs
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Update_ytd_sales" AND
type = "TR")
DROP TRIGGER Update_ytd_sales
GO
CREATE TRIGGER Update_ytd_sales
ON sales
FOR INSERT
AS
SELECT *
FROM inserted
UPDATE titles
SET ytd_sales = ytd_sales + qty
FROM inserted
WHERE titles.title_id = inserted.title_id
GO
您可以注意到,我们在 UPDATE 陈述式中使用 FROMtable_source(FROM inserted)子句来指明qty值来源于inserted资料表。请执行下面的 INSERT 陈述式,以检视来自该触发程序的结果:
INSERT INTO sales VALUES(7066, 1, "2000-03-07", 100, "Net 30",
"BU1111")
GO
您将看到如下的结果。第一组结果显示 inserted 数据表中所选择的数据列,然后显示(影响 1 个数据列)讯息(来自 UPDATE 陈述式),如下所示:
stor_id ord_num ord_date qty payterms title_id
------- -------- ----------------------- ---- -------- --------
7066 1 2000-03-07 00:00:00.000 100 Net 30 BU1111
(影响 1 个数据列)
(影响 1 个数据列)
建立 UPDATE 触发程序
接下来我们将建立一个 UPDATE 触发程序,当更新titles数据表中的price数据行时,该触发程序会检查price数据行,并验证价格上涨不超过 10%。如果超过了 10%,将以 ROLLBACK 陈述式来复原触发程序和呼叫触发程序的陈述式。如果在一个较大的交易中启动触发程序,整笔交易将被复原。在这个范例中,我们利用deleted和inserted数据表来测试价格的变化。该触发过程定义如下所示:
USE pubs
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Update_Price_Check" AND
type = "TR")
DROP TRIGGER Update_Price_Check
GO
CREATE TRIGGER Update_Price_Check
ON titles
FOR UPDATE
AS
DECLARE @orig_price money, @new_price money
SELECT @orig_price = price from deleted
PRINT "orig price ="
PRINT CONVERT(varchar(6), @orig_price)
SELECT @new_price = price from inserted
PRINT "new price ="
PRINT CONVERT(varchar(6), @new_price)
IF(@new_price > (@orig_price * 1.10))
BEGIN
PRINT "Rollback occurred"
ROLLBACK
END
ELSE
PRINT "Price is OK"
END
GO
执行以下的陈述式,来检视一本title_id为BU1111的价格,测试该触发程序:
SELECT price
FROM titles
WHERE title_id = "BU1111"
GO
价格是 $11.95。接下来,执行以下陈述式,将价格增加 15%:
UPDATE titles
SET price = price * 1.15
WHERE title_id = "BU1111"
GO
您会看到以下的结果:
orig price =
11.95
new price =
13.74
Rollback occurred
触发程序触动,打印出原价格及新价格,但由于价格增加超过 10%,因此将此交易复原。
现在检查价格,确认是否将修改回复。请使用下面的 T-SQL:
SELECT price
FROM titles
WHERE title_id = "BU1111"
GO
因价格重设至 $11.95(而非新设至 $13.75),所以修改确实被回复。现在,我们来测试若价格修改低于 10%,价格是否会被更新。按 10% 修改价格,用以下 T-SQL 测试结果:
UPDATE titles
SET price = price * 1.09
WHERE title_id = "BU1111"
GO
SELECT price
FROM titles
WHERE title_id = "BU1111"
GO
价格已更新至 $13.03,由于增加少于 10%,触发程序不会被回复。
当建立一个 UPDATE 触发程序时,可以指定触发程序仅在特定的数据列更新时,才执行陈述式。例如,您可以设定只有在price数据行被更新时,才检查该数据行,确认触发程序正确触发。请使用下面的 IF UPDATE 子句:
USE pubs
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Update_Price_Check" AND
type = "TR")
DROP TRIGGER Update_Price_Check
GO
CREATE TRIGGER Update_Price_Check
ON titles
FOR UPDATE
AS
IF UPDATE (price)
BEGIN
DECLARE @orig_price money, @new_price money
SELECT @orig_price = price from deleted
PRINT "orig price ="
PRINT CONVERT(varchar(6), @orig_price)
SELECT @new_price = price from inserted
PRINT "new price ="
PRINT CONVERT(varchar(6), @new_price)
IF(@new_price > (@orig_price * 1.10))
BEGIN
PRINT "Rollback occurred"
ROLLBACK
END
ELSE
PRINT "Price is OK"
END
GO
如果titles数据表的更新不包括price数据行(仅更新其它数据行),触发程序会跳过第一个 BEGIN 和 END 之间的陈述式,也跳过该触发程序。
要测试以上的触发程序是否如预期般跳过执行 BEGIN 和 END 之间的陈述式,您可以用下面的 T-SQL 陈述式,更新一个非price数据行的数据。以下的例子是将title_id为 BU1111 的这本书,更新其tyd_sales数据行的值。
UPDATE titles
SET ytd_sales = 123
WHERE title_id = "BU1111"
GO
您可以看到数据行的更新不在price数据行,外部 IF 条件回传 FALSE,因此输出结果不启动触发程序的打印陈述式。利用这种方式可以避免 SQL Server 处理不必要的陈述式。
建立 INSTEAD OF 触发程序
INSTEAD OF 触发程序让您能控制 INSERT、UPDATE 及 DELETE 陈述式执行时所发生的事件,主要适用于联合检视表的更新。一般而言,联合检视表是不可更新的,因为 SQL Server 不知道要更新哪些基底数据表。要让检视表支持更新,您可以在检视表中定义 INSTEAD OF 触发程序,以透过检视表修改多个基底数据表。请参照以下范例:
以下范例透过使用 T-SQL 陈述式,建立一个命名为TitlesByAuthor的检视表,这个检视表会参考author、titles和titleauthor数据表的数据。(本书 第十八章 可找到更多关于检视表的数据。)
USE pubs
GO
CREATE VIEW TitlesByAuthor
AS
SELECT authors.au_id, authors.au_lname, titles.title
FROM authors INNER JOIN
titleauthor ON authors.au_id = titleauthor.au_id INNER JOIN
titles ON titleauthor.title_id = titles.title_id
GO
建立检视表后,我们利用 T-SQL 来显示所有达到检视资格的资料列:
USE pubs
GO
SELECT *
FROM TitlesByAuthor
GO
au_id au_lname title
----------- -------------- --------------------------------------
238-95-7766 Carson But Is It User Friendly?
724-80-9391 MacFeather Computer Phobic AND Non-Phobic
Individuals:
756-30-7391 Karsen Computer Phobic AND Non-Phobic
Individuals:
267-41-2394 O'Leary Cooking with Computers:
724-80-9391 MacFeather Cooking with Computers:
486-29-1786 Locksley Emotional Security: A New Algorithm
648-92-1872 Blotchet-Halls Fifty Years in Buckingham Palace
Kitchens
899-46-2035 Ringer Is Anger the Enemy?
998-72-3567 Ringer Is Anger the Enemy?
998-72-3567 Ringer Life Without Fear
486-29-1786 Locksley Net Etiquette
807-91-6654 Panteley Onions, Leeks, and Garlic:
172-32-1176 White Prolonged Data Deprivation: Four Case
Studi
427-17-2319 Dull Secrets of Silicon Valley
846-92-7186 Hunter Secrets of Silicon Valley
712-45-1867 del Castillo Silicon Valley Gastronomic Treats
274-80-9391 Straight Straight Talk About Computers
267-41-2394 O'Leary Sushi, Anyone?
472-27-2349 Gringlesby Sushi, Anyone?
672-71-3249 Yokomoto Sushi, Anyone?
213-46-8915 Green The Busy Executive's Database Guide
409-56-7008 Bennet The Busy Executive's Database Guide
722-51-5454 DeFrance The Gourmet Microwave
899-46-2035 Ringer The Gourmet Microwave
213-46-8915 Green You Can Combat Computer Stress!
(影响 25 个数据列)
如果您试图在au_lname数据行中删除Carson,会出现下列讯息:
服务器:讯息 4405,层级 16,状态 1,行 1
由于此修改会影响多个基底数据表,检视表或函数 'TitlesByAuthor' 无法更新。
要避免上述讯息产生,现在我们用 INSTEAD OF 触发程序来执行删除的动作,以下我们会利用一个命名为Delete_It的 INSTEAD OF 触发程序来执行删除的动作,其 T-SQL 的操作如下:
________________________________________
说明
以下的操作方式并非真正在author数据表中删除数据列,而只是重新命名这个数据列。
________________________________________
USE pubs
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'Delete_It' AND
type = 'TR')
DROP TRIGGER Delete_It
GO
CREATE TRIGGER Delete_It
ON TitlesByAuthor
INSTEAD OF DELETE
AS
PRINT 'Row from authors before deletion...'
SELECT au_id, au_lname, city, state
FROM authors
WHERE au_lname = 'Carson'
PRINT 'Deleting row from authors...'
UPDATE authors
SET au_lname = 'DELETED'
WHERE au_lname = 'Carson'
PRINT 'Verifying deletion...'
SELECT au_id, au_lname, city, state
FROM authors
WHERE au_lname = 'Carson'
GO
利用以上的方式,即利用 T-SQL 陈述式自检视表中删除Carson,这个程序使 INSTEAD OF 触发程序触动,得到以下的输出结果:
Row from authors before deletion...
au_id au_name city state
----------- --------------- -------------------- -----
238-95-7766 Carson Berkeley CA
(影响 1 个数据列)
Deleting row from authors...
(影响 1 个数据列)
Verifying deletion...
au_id au_name city state
----------- --------------- -------------------- -----
(影响 0 个数据列)
建立 AFTER 触发程序
本章之前曾提到,AFTER 触发程序会在 SQL 陈述式动作触发之后才触动。您可以为每个触发动作(INSERT、UPDATE 或 DELETE)指定多个 After 触发程序。如果数据表有多个 After 触发程序的话,您可以使用sp_settriggerorder定义哪个 After 触发程序先触动,哪个最后触动。除了头尾两个 After 触发程序外,所有其它触发程序触动的顺序无法由您控制。以下是这个陈述式的语法:
sp_settriggerorder [@triggername =]'triggername',
[@order=] {'first' | 'last' | 'none'}
首先来看个例子。假定我们在一个数据表中定义了四个触发程序,分别命名为MyTrigger、MyOtherTrigger、AnotherTRigger和YetAnotherTrigger,当陈述式动作触发之后,让Another Trigger第一个触动,MyTrigger最后一个触动。
sp_settriggerorder @triggername = 'AnotherTrigger', @order = 'first'
go
sp_settriggerorder @triggername = 'MyTrigger', @order = 'last'
go
sp_settriggerorder @triggername = 'MyOtherTrigger', @order = 'none'
go
sp_settriggerorder @triggername = 'YetAnotherTrigger', @order = 'none'
go
上述所看到的「none」,使得介于第一个及最后一个触发程序之间启动的触发程序,以随机的顺序执行。由于随机的执行顺序是依触发程序的预设进行,因此不需特别执行sp_settriggerorder定义顺序。
建立巢状触发程序
巢状触发程序(nested trigger) 是由其它触发程序所启动的触发程序,和递归触发程序不同的地方在于,巢状触发程序并不自行启动,而是当修改事件产生时,才由启动其它的触发程序所启动。SQL Server 2000 和 SQL Server 7.0 相同,第一个触发程序可以启动第二个触发程序,第二个触发程序接着启动第三个触发程序,依此类推,可以高达 32 个层级的触发程序。在 SQL Server 2000 中,巢状触发程序的预设状态设为启用,nested triggers服务器设定参数可用来控制触发程序能否巢状触发。要停止巢状触发程序,可执行以下的指令:
sp_configure "nested triggers", 0
GO
将nested triggers设成0时,则不启动巢状触发程序;将nested triggers设成1时,则可启动巢状触发程序。
接下来是巢状触发程序的范例:当一本书的标题从titles数据表中删除时,会引起巢状触发程序,以串联的方式删除相对应的数据列。在本章 〈建立 DELETE 触发程序〉 一节中,我们曾建立单一的触发程序进行数据的串联删除。我们要沿用之前的范例,但首先要删除之前范例中的触发程序避免触动,然后建立三组触发程序;第二及第三组触发程序为巢状触发程序,语法如下:
USE pubs
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Delete_Title" AND
type = "TR")
DROP TRIGGER Delete_Title
GO
CREATE TRIGGER TR_on_titles
ON titles
FOR DELETE
AS
DELETE sales
FROM sales, deleted
WHERE sales.title_id = deleted.title_id
PRINT "Deleted from sales"
GO
CREATE TRIGGER TR_on_sales
ON sales
FOR DELETE
AS
DELETE roysched
FROM roysched, deleted
WHERE roysched.title_id = deleted.title_id
PRINT "Deleted from roysched"
GO
CREATE TRIGGER TR_on_roysched
ON roysched
FOR DELETE
AS
DELETE titleauthor
FROM titleauthor, deleted
WHERE titleauthor.title_id = deleted.title_id
PRINT "Deleted from titleauthor"
GO
欲执行触发程序,必须先删除数据表上外部索引键条件约束(如本章 〈建立 DELETE 触发程序〉 一节所示)。使用以下 DELETE 陈述式,测试是否正确执行所有触发程序。
DELETE
FROM titles
WHERE title_id = "PS7777"
GO
您可以看到如下的结果:
(影响 2 个数据列)
(影响 1 个数据列)
Deleted from titleauthor
(影响 2 个数据列)
Deleted from roysched
(影响 1 个数据列)
Deleted from sales
(影响 1 个数据列)
当一组巢状触发程序的任一层级失败,交易会被取消,且所有的数据修改将被复原至整组触发程序触动前的状态。
使用 Enterprise Manager 建立触发程序
要使用 Enterprise Manager 建立触发程序,您只需要在 触发属性 窗口中键入 T-SQL 陈述式即可,执行步骤如下:
1. 从 Enterprise Manager 中,在您想要建立触发程序的数据表名称上按右钮,开启 快捷菜单 窗口。选择 所有工作/管理触发程序 ,就会出现 触发属性 对话框,如图 22-1 所示。
图22-1 「触发属性」对话框
2. 在 文字 中输入触发程序的 T-SQL 语法(图 22-2)。
3. 按一下 检查语法 按钮以核对语法。语法如正确会显示 语法检查成功 。
窗口(图 22-3)。按一下 确定 以建立触发程序。现在触发程序 Print_Update 出现在 名称 下拉式清单中,如图 22-4 所示。
4. 触发属性 窗口保持开启的状态,使您可以在资料表上建立其它的触发程序。如果您没有其它的触发程序要建立,按一下 关闭 。
图22-2 输入触发程序语法后的「触发属性」对话框
图22-3 语法检查成功窗口
图22-4 下拉式清单中新建立触发程序的名称
管理触发程序
在本节中,我们先学习使用 T-SQL 管理触发程序,然后再学习如何使用 Enterprise Manager 管理。
使用 T-SQL 管理触发程序
T-SQL 指令可用于管理触发程序。您可以检视触发程序程序语法、存在于特定数据表上的触发程序、更改触发程序语法、删除触发程序、启动或停止触发程序。本节中将会说明这些选项。
检视触发程序语法
sp_helptext和sp_helptrigger两种系统预存程序能提供检视触发程序的相关信息。若要显示用来建立程序的文字,可在预存程序名称之前执行sp_helptext。举例来说,若要显示之前建立Print_Update这个触发程序中的文字定义,可使用下面的指令:
USE MyDB
GO
sp_helptext Print_Update
GO
输出结果如下:
Text
-----------------------------
CREATE TRIGGER Print_Update
ON Bicycle_Inventory
FOR UPDATE
AS
PRINT "The Bicycle Inventory table was updated."
检视数据表中现存的触发程序
要检视存在特定数据表中的触发程序(或检视触发程序是否存在),可在数据表名称之前执行sp_helptrigger预存程序。要检视范例数据表MyTable的触发程序,请使用下面的指令:
USE MyDB
GO
sp_helptrigger MyTable
GO
输出结果如下:
trigger_name trigger_owner isupdate isdelete isinsert isafter isinsteadof
------------ ------------ --------- --------- -------- -------- ---------
Print_Update dbo 1 0 0 1 0
(影响 1 个数据列)
输出结果显示触发程序名称、触发程序拥有者,以及启动触发程序的数据修改事件类型。如果触发程序因该类型的数据修改事件启动,输出结果数据行isupdate、isdelete、isinsert、inafter和isinsteadof的值则为1,如果没有启动则为0。如果触发程序因多种类型的数据修改事件触动,就会有多个资料行含有值1。
使用 ALTER TRIGGER
要改变先前建立的触发过程定义,可以删除后重建触发程序,也可以使用 ALTER TRIGGER 陈述式,这个陈述式使用和 CREATE TRIGGER 陈述式相同的语法。如果要改变触发程序,必须重新定义整个触发程序。举例来说,要改变Print_Update触发程序对Bicyle_Inventory的数据修改方式(当 INSERT 或 UPDATE 陈述式执行时即会触动),可执行以下语法:
USE MyDB
GO
ALTER TRIGGER Print_Update
ON MyTable
FOR UPDATE, INSERT
AS
PRINT "Bicycle_Inventory was updated or a row was inserted"
GO
现在先前所定义的触发程序已被更新过的版本所代替。现在如果在Bicycle_Inventory上执行 UPDATE 或 INSERT,触发程序即会触动,以下为执行的语法:
INSERT INTO Bicycle_Inventory VALUES ("Trek",1,"Lance S.E.",1,0,1)
GO
UPDATE Bicycle_Inventory
SET in_stock = 1
WHERE model_name = "Lance S.E."
GO
使用 DROP TRIGGER
要从数据表中完全删除触发程序,可利用 DROP TRIGGER 陈述式。删除的语法如下:
DROP TRIGGERtrigger_name
要删除Pring_Update触发程序,可使用下面的陈述式:
USE Bicycle_Inventory
GO
DROP TRIGGER Print_Update
GO
如果现在检视存在于MyTable数据表上的所有触发程序,会发现该触发程序已不存在,可以使用以下语法检视:
USE MyDB
GO
sp_helptrigger MyTable
GO
________________________________________
说明
如果您删除一个数据表,该数据表上的所有触发程序都会被自动删除。
________________________________________
启动和停止触发程序
利用 ALTER TABLE 陈述式,不用删除触发程序即可变更触发程序的定义,由于每个触发程序都定义在一个特定数据表上,所以使用 ALTER TABLE 陈述式来代替 ALTER TRIGGER 陈述式。使用以下语法,重建本章中第一个范例的触发程序:
USE MyDB
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = "Print_Update" AND
type = "TR")
DROP TRIGGER Print_Update
GO
CREATE TRIGGER Print_Update
ON Bicycle_Inventory
FOR UPDATE
AS
PRINT "The Bicycle_Inventory table was updated"
GO
触发程序在一开始会自动启用。关闭触发程序可使它不被启用(直到重新启用为止),但是其定义仍然存在于该数据表上。可使用下面的 DISABLE TRIGGER 选项关闭触发程序:
ALTER TABLE Bicycle_Inventory
DISABLE TRIGGER Print_Update
GO
现在当Bicycle_Inventory上发生更新时,Print_Update触发程序不会触动。欲重新启动触发程序,可利用 ENABLE TRIGGER,语法如下:
ALTER TABLE Bicycle_Inventory
ENABLE TRIGGER Print_Update
GO
ENABLE TRIGGER 和 DISABLE TRIGGER 子句可使触发过程定义保持不变,视需要而开启或关闭该触发程序即可。
使用 Enterprise Manager 管理触发程序
您也可以使用 Enterprise Manager 管理触发程序,尽管可使用图形化使用接口管理触发程序,仍需撰写 T-SQL 程序语法。
删除触发程序
要使用 Enterprise Manager 删除触发程序,按照下面的步骤:
1. 在数据表名称上按右钮叫出快捷菜单,选择 所有工作/管理触发程序 。
2. 从 名称 下拉式清单中选择触发程序名称,然后按一下 删除 。
3. 当询问您是否确定想要删除触发程序时,按一下 确定 (如图 22-5)。
图22-5 确定是否删除触发程序的对话框
修改触发程序
按照以下步骤,即可使用 Enterprise Manger 修改触发程序
1. 在数据表名称上按右钮叫出快捷菜单,选择 所有工作/管理触发程序 。
2. 从 名称 下拉式清单中选择触发程序名称。
3. 在 文字 方块中编辑 T-SQL 语法,使用 Ctrl+Tab 键来排缩触发程序的文字。您可以使用 CREATE TRIGGER 或 ALTER TRIGGER 陈述式,不论使用其中的哪一种,SQL Server 会删除现存的触发程序并且使用其中一种陈述式重建触发程序,依这样的操作,您不会得到在使用交互式 OSQL 或ISQL 时,所会出现的错误讯息。完成编辑后,按一下 确定 ,SQL Server 将会自动修改触发过程定义。
本章总结
在本章中,我们学习了何谓触发程序,并认识了五种类型的触发程序,分别是 INSERT、UPDATE、DELETE、ALTER 和 INSTEAD OF,还学到了使用 T-SQL 和 Enterprise Manager 建立及管理触发程序。下一章,我们将利用 ADO 及 XML,透过因特网存取 SQL Server 2000。