Yukon 中的 TSQL:TSQL强大的新语法给予 SQL Server 进一步的可编程性


原著:Itzik Ben-Gan

译者:yy2better


原文出处:MSDN Magazine Feb 2004(T-SQL in Yukon)

下载关于此文章的代码:TSQLinYukon.exe (117KB)

  本文基于 Microsoft 代号“Yukon”的SQL Server Beta 1版本,这里包含的所有信息都可能改变。
  注:本文是在产品发布前写出来的,所以,我们不保证此文包含的任何细节与正式售卖品完全一致。本文付诸印刷时,所有描述该产品的信息仅能应用于计划目的。有关信息在任何时候都有可能改变,恕不事先通知。

摘要
  即将发布的SQL Server版本里的T-SQL语言将比前一版本更强大和更灵活。其增加和增强部分包括通过TRY/CATCH结构进行错误处理、SNAPSHOT隔离和WAITFOR增强。同样重要的有:BULK行集提供者、通用表表达式、递归查询、PIVOT和UNPIVOT算子,及更多。本文介绍了这些特性,以便读者对SQL Server的下一版本做好准备。
  SQL Server™的下一版本,代号为“Yukon”,其Beta 1版本的发布引入了一组T-SQL增强功能和新特性,这些可改进你的表示能力、错误处理和性能。本文中我将论述一些重要的增强功能和新特性,其包括错误处理、递归查询和隔离。我重点论叙错误处理和相关的改进,并简要描述其他增强功能。
  除了这里描述的特征外,T-SQL有很多我未能深入讲述的其他重要增强;因为其中一些从概念上将并不是新的另一些则值得独立的论述。这些包括消息和Service Broker平台、分区和XML增强。更多关于Yukon中XML增强请参阅本期Bob Beauchemin所写的"Yukon 中的 XML:新版本展示原始 XML 类型和高级数据处理" .

错误处理
  SQL Server Yukon Beta1版本T-SQL中引入了新的便于错误处理TRY/CATCH结构。这种结构允许你捕获事务异常中断错误,甚至那些在SQL Server上一版本中导致批处理终止的错误(转换错误、死锁等等)。新结构不能捕获的错误类型是那些导致会话终止的错误(通常是严重等级21及以上的错误,例如硬件错误)。典型地,你的错误处理代码看起来象Figure 1所示。
  开启XACT_ABORT设置后,SQL Server将对任意错误视为事务异常中断错误,从而允许它被捕获并处理。TRY块内,发生在显示事务中任何错误导致控制转移到CATCH块,它紧跟着TRY块。如果没有错误发生,CATCH块被跳过。如果想检查被抛出的错误类型并相应地作处理,必须在CATCH块开始处将@@error的返回值保存到变量中,然后再开始检查它。另外,通过@@error返回的值可能不正确,因为包括DECLARE在内的任意表达式都可能会改变它。
  当在位于TRY块内的一事务中一个事务异常中断错误发生后,控制权就被转移到CATCH块,事务进入毁灭(doomed)状态。直到显示执行ROLLBACK命令,系统才会释放锁,持续工作才会被回滚。直到执行ROLLBACK,你才会被允许发起任何需要开启一个隐式或显示事务的活动。能检查在发生错误的事务里被改变的资源内容,通过这可了解资源变化,然而,要执行需开启事务的任何纠正措施,你必须执行ROLLBACK。注意,为了捕获发生在CATCH块中的错误,必须使用嵌套TRY/CATCH结构编写代码。看一个更详细的例子,首先创建ErrorLog表,在此表中错误处理代码审计(audit)注解,然后创建T1和T2表,并对它们执行查询,如我在Figure 2用代码所写的。
  接着,在一新连接(称其为连接1)中运行Figure 3中的脚本(称其为脚本1)。脚本1设置锁超时时间为30秒,死锁优先级为低,此脚本在正常优先级运行进程中的死锁场景里志愿成为死锁牺牲品。TRY块中的代码UPDATE表T1,等待10秒,然后从表T2中SELECT。如果事务无错误完成,一行数据会被插入ErrorLog表,其注释说明它成功完成。
  CATCH块被设计用于通过重试逻辑来捕获主键违规错误、锁超时错误和死锁错误。可在代码开始处通过改变对变量@retry的值来设置所需的重试次数;它当前设置为2。
  第一次运行Figure 3处代码后,检查ErrorLog表内容。注意事务已经成功完成。要测试一个主键违规错误,请打开一新连接(称其为连接2)并运行如下代码:

INSERT INTO T1 VALUE(3)       
返回连接1,再次运行脚本1。如果检查表ErrorLog内容,会看到一个主键违规错误已被记录。转到连接2,运行下面命令以删除刚插入的行:
DELETE FROM T1 WHERE col1 = 3      
要测试锁超时错误,在连接2中运行下面代码:
BEGIN TRAN
UPDATE T1 SET col1 = 1      
返回连接1,再次运行脚本1。大约30秒后,你会得到一个错误。检查ErrorLog内容,会发现一个锁超时错误已被记录。转到连接2,执行ROLLBACK命令回滚事务。
要测试死锁,转到连接2,粘贴下面代码,但还不要运行它:
DECLARE @i AS INT
BEGIN TRAN
  SET @i = 1
  WHILE @i <= 2
  BEGIN
    UPDATE T2 SET col1 = 2
    WAITFOR DELAY ''''00:00:10''''
    SELECT * FROM T1
  WAITFOR DELAY ''''00:00:05''''
  SET @i = @i + 1
END
ROLLBACK      
  转到连接1,运行脚本1代码,然后立即在连接2中运行代码。约一分钟后,你会看到连接1发生错误。检查ErrorLog内容,注意到表中有死锁错误后两个重试尝试和第三个未导致错误的成功尝试。查询ErrorLog表,检查它的内容。
  最后,如果你想在TRY块内抛出自己的事务异常错误,你可调用带TRAN_ABOAT选项的RAISERROR命令。

SNAPSHOT隔离
  Yukon引入一名称为SNAPSHOT的新隔离级,它允许你以一种模式工作,在这种模式下,倘若为读取者提供需要的数据一个已提交版本,则写入者不会阻塞读取者。SQL Server Yukon在表tempdb中维护一个链接队列,它们跟踪行改变并为读取者构造一个稍老的已提交版本数据。当UPDATE冲突不是常见时,这种隔离有益于乐观锁定。如果进程1获取数据,后来试图修改它;如果进程2在进程1取数据和修改数据之间已修改同样的数据,那么SQL Server会产生一个关于进程1试图修改的错误,错误是因为冲突产生的。然后,进程1可设法重新执行事务。这种模式在update冲突不常见情景下是有效率的。
  为了允许工作在SNAPSHOT隔离级,你必须开启数据库选项ALLOW_SNAPSHOT_ISOLATION, 这你将很快看到。要模拟一个写入者不阻塞读取者的场景,首先创建testdb数据库,开启适当的数据库选项,并用下面代码创建T1表同时设置datacol为“Version1”:
CREATE DATABASE testdb
GO
USE testdb
ALTER DATABASE testdb SET ALLOW_SNAPSHOT_ISOLATION ON

CREATE TABLE T1
(
keycol  INT         NOT NULL PRIMARY KEY,
datacol VARCHAR(10) NOT NULL
)
INSERT INTO T1 VALUES(1, ''''Version1'''')      
在连接1中执行下列代码,它开启一个事务并改变datacol值为“Version2”:
USE testdb
BEGIN TRAN
  UPDATE T1 SET datacol = ''''Version2'''' WHERE keycol = 1
  SELECT * FROM T1      
转到连接2,运行下列代码,它设置会话隔离级为SNAPSHOT并取回T1的内容:
USE testdb
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
SELECT * FROM T1      
  注意你取回的是“Version1”,即使连接2已将它改变为“Version2”(但是还没有提交改变)。
  现在转到连接1,提交事务并用COMMIT命令关闭所有连接。要尝试乐观锁定,打开两个新连接,在连接1中运行下列代码,它设置会话隔离级为SNAPSHOT,开启一个事务,然后从T1获取数据:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
  SELECT * FROM T1            
转到连接2,执行UPDATE:
UPDATE T1 SET datacol = ''''Version3'''' WHERE keycol = 1      
返回连接1,设法update你以前获取的同样数据,这些数据已在连接2中被修改:
UPDATE T1 SET datacol = ''''Version4'''' WHERE keycol = 1            
  你应该得到一个错误,它通知你SQL Server不能使用SNAPSHOT隔离级去存取数据库testdb的表T1和应重试你的事务。

WAITFOR命令
  Yukon中WAITFOR命令在数个方面被加强。除了等待指定间隔或等到某个时间点,现在还可请求等待至少影响一行的T-SQL语句。可指定此命令等待下列语句中的一种:SELECT、INSERT、UPDATE、DELETE或RECEIVE。前四种是不言自明的;RECEIVE指从队列收到一个消息。如果要在指定的毫秒数之后停止等待,可随意指定超时时间。WAITFOR命令语法是:
WAITFOR() [,TIMEOUT ]      
  Yukon中另一增强允许返回从数据操纵语言(INSERT,UPDATE,DELETE)而不只是SELECT语句的输出。新的OUTPUT子句允许请求列的旧或新数据通过引用INSERTED和DELETED表被返回,类似在触发器中引用它们。你甚至能指定INTO子句并定向其输出到一个表变量。另一增强是允许在修改语句里指定READPAST提示,这样就可跳过锁定行。
  使用上述增强的一个例子是,数个进程等待DELETE语句从一个表中至少删除一行,然后定向其输出到表变量中,同时每个进程并行处理数据的不同部分。要证明这点,创建下表MsgQueue:
USE tempdb
CREATE TABLE MsgQueue
(
  msgid   INT         NOT NULL IDENTITY PRIMARY KEY,
  msgdata VARCHAR(15) NOT NULL
)            
  打开一个或多个连接,在每个连接中运行下列代码以定时插入新消息到表中:
SET NOCOUNT ON
USE tempdb

WHILE 1 = 1
BEGIN
  INSERT INTO MsgQueue VALUES(''''Msg'''' +
    CAST(CAST(RAND()*1000000000 AS INT) AS VARCHAR(10)))
  WAITFOR DELAY ''''00:00:01''''
END            
  接着需打开数个其他新连接,并在每个连接中运行Figure 4中代码以模拟对新到达消息的处理。

BULK行集提供者(BULK Rowset Provider)
  Yukon引入了新BULK行集提供者,你能在OPENROWSET函数中使用它以有效地通过关系方式存取文件。可类似于使用BULK INSERT语句方式使用此行集提供者,但不必发送其输出到表中。然而,你确实必须指定一个与使用bcp.exe或BULK INSERT语句相同的格式文件。下列代码说明如何使用格式文件c:\temp\textfile1.fmt来存取文件c:\temp\textfile1.txt,倘若将结果表命令为C,结果列命令为col1、col2和col3:
SELECT col1, col2, col3
FROM OPENROWSET(BULK ''''c:\temp\textfile1.txt'''',
       FORMATFILE = ''''c:\temp\textfile1.fmt'''') AS C(col1, col2, col3)      
  除了FORMATFILE选项,还能在OPENROWSET函数里指定下列选项:CODEPAGE、DATAFILETYPE、FIELDTERMINATOR、FIRSTROW、LASTROW和ROWTERMINATOR。也可以使用INSERT SELECT来有效地载入数据到表中和为装载选项指定表提示:
INSERT INTO MyTable WITH (BULK_CHECK_CONSTRAINTS)
  SELECT col1, col2, col3
  FROM OPENROWSET(BULK ''''c:\temp\textfile1.txt'''',
         FORMATFILE = ''''c:\temp\textfile1.fmt'''') AS C(col1, col2, col3)            
  能指定表提示的其他选项包括:BULK_BATCHSIZE, BULK_FIRE_TRIGGERS, BULK_KEEPIDENTITY, BULK_KEEPNULLS, BULK_KILOBYTES_PER_BATCH, BULK_MAXERRORS,和ROWS_PER_BATCH。
  通过使用BULK行集提供者,可以比以前正规的DML语句更容易地载入文件到表中。现在,对于大对象可以不被限制在TEXT、NTEXT和IMAGE数据类型,你还可使用VARCHAR(MAX),NVARCHAR(MAX)和VARBINARY(MAX)数据类型。新MAX选项允许和正规数据类型一样的方式操纵大对象。例如,下列UPDATE语句存储文本文件到表列中,此列被定义为VARCHAR(MAX):
UPDATE LOBs
  SET clob_col =  (SELECT clob_data
                   FROM OPENROWSET(BULK ''''c:\temp\textfile1.txt'''',
                          SINGLE_CLOB) AS C(clob_data))      
WHERE keycol = 1                                
  SINGLE_NCLOB选项通知SQL Server大对象是字符格式的。类似地,SINGLE_CLOB指定大对象为规则字符格式,SINGEL_BLOB为二进制格式。返回列的名称为BulkColumn,然而如上一代码片段所示,可以为它指定自己的列名。

TOP选项
  Yukon中T-SQL的TOP选项有两个重大的增强。其一,可以为TOP指定一个表达式参数,此表达式能包含变量或甚至一个自包含查询。其二,可以在修改性DML(INSERT, UPDATE, DELETE)中使用TOP选项。
  要指定表达式,必须将它包含在圆括号中。表达式当不使用PERCENT选项时为BIGINT数据类型,当使用PERCENT选项时为范围在0~100的浮点类型。下列代码表明如何使用一个带变量的表达式来返回AdventureWorks数据库SalesOrderHeader表的最先满足请求数目的订单:
USE AdventureWorks

DECLARE @n AS BIGINT
SET @n = 5

SELECT TOP (@n) *
FROM SalesOrderHeader AS SOH
ORDER BY OrderDate, SalesOrderID            
  排序时同种情况下SalesOrderID优先。类似地,下面例子示范如何使用PERCENT选项返回最先满足请求百分比的订单:
DECLARE @p AS FLOAT
SET @p = 0.01

SELECT TOP (@p) PERCENT *
FROM SalesOrderHeader AS SOH
ORDER BY OrderDate, SalesOrderID            
如果SalesOrderHeader表包含31519行,31519×0.0001结果向上取整为4, 你会得到4行结果。
  允许TOP与修改性DML一起使用的主要动机是代替SET ROWCOUNT选项,这个选项SQL Server没有很好优化。SET ROWCOUNT选项通常在批处理中处理大量数据时与修改性语句协同使用,以避免事务日志爆炸性增加并防止个体锁逐步增加到满表锁。要领会如何使用新TOP功能在批处理中删除行,首先拷贝表SalesOrderHeader内容到表MySalesOrderHeader,然后创建OrderData和SalesOrderID列的索引。这些通过运行下列代码实现:
SELECT *
INTO MySalesOrderHeader
FROM SalesOrderHeader

CREATE UNIQUE CLUSTERED INDEX idx_uc_OrderDate_SalesOrderID
  ON MySalesOrderHeader(OrderDate, SalesOrderID)            
要以批处理量为1000的方式删除所有订单年份比2003年早的行,用下列代码:
WHILE 1 = 1
BEGIN
  DELETE TOP (1000) 
  FROM MySalesOrderHeader WHERE OrderDate < ''''20030101''''

  IF @@rowcount < 1000 BREAK
END            
SQL Server比使用SET ROWCOUNT更有效优化此代码。现在可以删除MySalesOrderHeader表:
DROP TABLE MySalesOrderHeader            
APPLY算子
  APPLY是一新的可在查询语句FROM子句中使用的关系算子。它允许对外部表中的每行调用表值(table-valued)函数,并可选择性地将外部表的列作为函数参数。APPLY算子有两种形式:CROSS APPLY和OUTER APPLY。前者不会返回外部表的行,如果表值函数返回空集给它,然而后者返回NULLS行而不是函数的列。要使用APPLY算子,首先创建下列Arrays表,它存储多组以逗号为间隔的值:
CREATE TABLE Arrays
(
  arrid INT NOT NULL IDENTITY PRIMARY KEY,
  array VARCHAR(7999) NOT NULL
)

INSERT INTO Arrays VALUES('''''''')
INSERT INTO Arrays VALUES(''''10'''')
INSERT INTO Arrays VALUES(''''20,40,30'''')
INSERT INTO Arrays VALUES(''''-1,-3,-5'''')            
  接着,创建fn_splitarr表值函数,它接受一队列为参数并返回一个包含个体元素和他们的位置的表(见Figure 5)。要测试函数,运行此代码:
SELECT * FROM fn_splitarr(''''20,40,30'''')            
输出应与下面相似:
pos         value
---         -----
1           20
2           40
3           30            
现在,用CROSS ARRAY算子对表Arrays中每行调用函数:
SELECT A.arrid, F.*
FROM Arrays AS A
  CROSS APPLY fn_splitarr(array) AS F            
然后对照下面检查输出:
arrid       pos         value
-----       ---         -----
2           1           10
3           1           20
3           2           40
3           3           30
4           1           -1
4           2           -3
4           3           -5            
注意,表Arrays中arrid为1的行没有返回,因为函数对其返回空集。如果要返回表Arrays中所有行,且不管函数是否为它们返回行,使用OUTER APPLY。
  支持APPLY算子的额外好处是能引用表值函数和在子查询中指定外部表的列为参数。例如,下列代码返回表Arrays中元素个数和小于和等于10的所有记录。
SELECT *
FROM Arrays
WHERE (SELECT SUM(value) FROM fn_splitarr(array)) <= 10            
通用表表达式(Common Table Expressions )和递归查询
  通用表表达式(CTEs)允许写命名表表达式,它仅仅在查询中存活。以简单的形式,他们能提供混合功能的视和导出表。譬如视,CTE在外部查询里可被多次引用;譬如导出表,它仅在查询期存活。以较复杂的形式,可以写递归CTEs,它让操纵树和图更容易和更有效。
  你可以用WITH子句定义CTE,WITH后跟随CTE名称;并可选择性地提供一列置于圆括号内的结果列别名。后面跟随AS子句和圆括号,圆括号中包含CTE查询表达式。最后,提供一个引用CTE结果的外部查询。在CTE查询表达式里,只要你喜欢你就能引用变量。
  Figure 6中代码显示一个简单例子,写的是非递归CTE,它返回每天顾客的销售定单额。显然,不使用CTE也可以达到同样结果,但是假使对于每行都要返回上年总额和与本年度的差额。如果选用导出表,则必须在一导出表中指定本年度的查询,在另一导出表中指定上一年度的查询,然后用外部查询连接两者。而用CTE,只需写单个查询,就可返回每年总额,然后用外部查询两次引用它。(见Figure 7
  然而,CTE真正的威力在于其递归形式。你可在CTE圆括号里定义独立或反引用到此CTE的查询。独立查询(没有引用此CTE名称)被称为锚成员(anchor members),它们只能被调用一次。反引用到此CTE名称的查询被称为递归成员,它们可被重复调用直到查询不返回行。通过使用UNION或UNION ALL算子,锚成员可互相追加,选择哪个算子取决于你是否要去除重复记录。必须使用UNION ALL算子追加递归成员。
  要示例递归CTE是有用的,设想数据库AdventureWorks中表BillOfMaterials。此表代表了典型的产品配件的材料清单场景,它们组成无环的有向图。每个产品由其他产品装配起来,它也可能被装配到其他产品,但没有循环关系。这种产品装配关系可由AssemblyID列和ComponentID列表示。PerAssemblyQty列包含ComponentID代表的产品的数量,对于每个个体产品则由AssemblyID代表。已废弃的关系在ObsoleteDate列中指定。如果你只对非废弃数据感兴趣,你应检查此列是否为NULL。表中还有其它有用信息,包括测量单位,但为了讲述此例,其他列将被忽略。
  Figure 8中的代码向ProductID 210的分解视输出数据。Figure 9显示了此视的摘录和描绘了产品间的包含关系。CTE体内,第一个查询没有引用CTE名称,所以它是锚成员,如上所述它只被调用一次。注意此查询寻找CompentID为210和assembly ID为NULL的行,这意味着它是顶层产品。此查询证实这个关系不是废弃的,它返回ComponentID和数量。递归成员返回包含在装配件中的产品,这些装配件由上一步执行CTE名称和BillOfMaterials表的JOIN而返回。第一次调用递归成员时,上一步是锚成员返回的结果。第二次调用递归成员时,上一步是第一次递归成员调用返回的结果,如此下去,直到递归成员返回空集。
  递归成员通过此部件的数量增加上一步的数量来计算部件的累计数量。外部查询对CTE名称的引用获取了所有锚成员和递归成员调用的统一结果。外部查询连接CTE与Products表以获取产品名称和产生如Figure 10所示的90行(已缩截的)。每个部件在输出中超过一次出现,例如产品835,因为它参与不同的装配。可以修改外部查询来以productID和name排序结果,这样可获取每个产品的总数。代码类似于Figure 8,但外部查询类似下面:
SELECT B.ProductID, P.Name, 
   SUM(B.Qty) AS TotalQty
FROM BOMCTE AS B
  JOIN Product AS P
    ON P.ProductID = B.ProductID
GROUP BY B.ProductID, P.Name
ORDER BY B.ProductID;            
如果怀疑循环和想限制递归调用的次数,可紧跟在外部查询指后定MAXRECURSION选项:
WITH...
outer_query
OPTION(MAXRECURSION 30)            
  当CTE超过指定限制,此选项使SQL Server抛出错误。当没有指定此选项时,SQL Server默认设置为100。如果不想由限制,必须指定其为0。注意可编写检查循环关系的自定义代码,但这超出本文的范围。

PIVOT算子
  SQL Server Yukon中新的PIVOT算子允许编写交叉查询,从而使行转换成列。UNPIVOT算子起相反的作用——使已PIVOT数据从列转换成行。当你想返回每个销售人员每年总销售订单额,Figure 11显示在数据库中使用PIVOT算子的结果,每年的数据显示于不同列。
  当使用PIVOT算子时需要注意的重要事情是:要为它提供一个查询表达式,此表达式使用视、导出表或CTE来返回只感兴趣的列。原因是PIVOT在幕后对所有没有被算子显示引用的列执行了隐式的GROUP BY操作。此例中,你需要salesperson ID,订单年份和订单数据:
USE AdventureWorks

SELECT
  SOH.SalesPersonID, 
  YEAR(SOH.OrderDate) AS OrderYear,
  SOD.OrderQty * SOD.UnitPrice AS OrderValue
FROM SalesOrderHeader AS SOH
  JOIN SalesOrderDetail AS SOD
    ON SOD.SalesOrderID = SOH.SalesOrderID            
  SQL Server计算出,“GROUP BY”列清单应是输入表中列清单,它们没有被合计函数或IN子句里PIVOT算子显示引用。因此,你不会在隐式GROUP BY列清单中得到不需要的列,你需要为PIVOT算子提供一个输入表,它仅包含对合计函数、IN子句和隐式GROUP BY感兴趣的列。这可以通过使用CTE或导出表获取,其包含返回只感兴趣列的上一查询。
  Figure 12代码示范如何在CTE中使用查询,和让外部查询对CTE结果执行PIVOT操作。SUM(OrderValue)告知PIVOT去计算哪个聚合以组合要PIVOT列的单元。FOR子句告知PIVOT哪个源列包含要转换到结果列的数据。IN子句包含将表现为列名的数据列表。
  SQL Servr需要你在IN子句显示指定要转换成结果列的数据列表。不能使用静态查询并让SQL Server计算出OrderYear所有的明显数据。要达到此目的,必须使用动态执行去动态构建字符串,如Figure 13代码所示。
  要了解UNPIVOT算子的运作,首先创建SalesPivoted表,它可通过运行Figure 12中的查询和在FROM子句前加上“SELECT INTO SalesPivoted”得到(见Figure 14)。UNPIVOT算子的参数很类似于PIVOT参数。但此次需指定一结果列的名称,此结果列将包含所有一列中已PIVOT的单元的数据。FOR子句后,需指定另一结果列的名称,此列将存储已PIVOT列的名称为列数据。IN子句后跟随圆括号,指定需要UNPIVOT的列清单:
SELECT *
FROM SalesPivoted
  UNPIVOT(OrderValue 
    FOR OrderYear IN([2001], [2002], [2003], [2004])) AS U            
  UNPIVOT不会返回含有NULL值单元的行。要清空刚才在数据库中已建立的表和索引,运行下列代码:
DROP INDEX SalesOrderHeader.idx_nc_OrderDate
DROP TABLE SalesPivoted            
触发器和通知
  SQL Server Yukon Beta1版本引入数据定义语言(DDL)触发器的支持,从而允许跟踪DDL操作和作相应反应,并可选择性地回滚活动。DDL触发器以同步方式工作,其紧跟触发事件后,类似于SQL Server上一版本触发器的工作方式。SQL Servr通过使用通知也支持异步事件消费机制,它可让你当某种事件发生后被通知。
  下列触发器被创建于数据库级,它捕获所有DROP TABLE企图:
CREATE TRIGGER prevent_drop_table ON DATABASE FOR DROP_TABLE
AS
RAISERROR(''''Not allowed to drop tables.'''', 10, 1)
ROLLBACK

-- For debug
PRINT ''''DROP TABLE attempt in database '''' + DB_NAME() + ''''.''''
PRINT EventData()
GO            
  可以定义触发器对指定DDL事件触发,这些事件有CREATE_TABLE、DROP_TABLE、ALTER_TABLE、CREATE_VIEW等等,或者,如果想触发器对数据库中所有DDL事件进行触发,可指定DDL_DATABASE_LEVEL_EVENTSXUAN。触发器内,可调用EventData函数以返回启动触发器的有关进程和活动的信息。可检查此函数返回的XML并作相应处理。
  要测试触发器,首先通过下列代码创建表TestDrop和插入一行数据:
CREATE TABLE TestDROP(col1 INT)
INSERT INTO TestDROP VALUES(1)            
接着,尝试删除表:
DROP TABLE TestDROP      
  DROP企图被捕获,一条信息被打印以指示不允许删除此表。同样,为调试所需,EventData函数的返回值以XML格式被打印出。(实际上,触发器内可检查XML数据,它包含很多有用信息,以决定哪个过程行为最适合你需要。例如,可防止一天中某时刻删除某表。)触发器回滚活动,因而表没有从数据库中删除。要删除触发器,执行下列语句:
DROP TRIGGER prevent_drop_table ON DATABASE            
  也可以创建捕获服务级别事件的触发器。例如,下面触发器捕获登录操作事件,如创建、改变或删除一次登录。
CREATE TRIGGER audit_ddl_logins ON ALL SERVER
  FOR CREATE_LOGIN, ALTER_LOGIN, DROP_LOGIN
AS
PRINT ''''DDL LOGIN took place.''''
PRINT EventData()
GO            
  此例中,当事件发生后触发器仅仅打印通告信息和事件的详细信息。但是,当然,可检查事件详细内容并作相应的处理。要测试触发器,运行下列代码,然后看看结果:
CREATE LOGIN login1 WITH PASSWORD = ''''123''''
ALTER LOGIN login1 WITH PASSWORD = ''''xyz''''
DROP LOGIN login1            
一DDL登录事件被识别,然后事件数据以XML格式产生。如果你愿意,可检查事件数据和审查对你重要的信息。
  如果要删除触发器,运行下列代码。
DROP TRIGGER audit_ddl_logins ON ALL SERVER      
总结
  Yukon提供的T-SQL增强和新特性允许你更有效操纵数据,更容易开发应用程序,和增加了错误处理能力。当处理数据操作时,T-SQL仍然是SQL Server中最佳开发选择,并且现在有了更丰富的开发环境。为更容易地试验这些新特性集,本文描述的所有例子的源码可从文章开首的链接处下载。
要了解背景信息,请查阅:
SQL Server
http://www.sqlmag.com

作者简介
  Itzik Ben-Gan 是 Solid Quality Learning 公司的主要顾问。他在国际上教学、演讲、写作和咨询相关 SQL Server 信息和知识。Itzik合著了Advanced Transact-SQL for SQL Server 2000 (APress, 2000)这本书。他管理以色列SQL Server用户组,并是一位SQL Server MVP。他的联系邮件 :itzik@solidqualitylearning.com

原文地址:https://www.cnblogs.com/huqingyu/p/25589.html