Transact-SQL 示例 - 觸發器的基礎及應用
閱讀提示:本篇文章內所使用到的數據庫為AdventureWorks OLTP
I:觸發器介紹
1.在開始介紹觸發器之前我們先回顧一下T-SQL的專業詞匯DDL, DML
| 詞匯 | 中文解釋 | 英文 |
| DDL | 數據定義語言 | data definition language |
| DML | 數據操作語言 | data manipulation language |
DDL:定義數據庫,表,視圖及其他對象的命令語句如:create database, create table, create view, create xxx, alter xxx, drop xxx等
DML:對數據庫內的表或視圖進行添加,修改,刪除操作的命令語句如:insert, update, delete.在這里注意一點,select是屬于DQL(Data Query Language, 數據查詢語言)而非DML.
2.什么是觸發器?它的用途是什么?
觸發器主要用于幫助SQL Server實現數據完整性約束.當用戶對現有數據庫的架構(schema)及表數據(包括視圖數據)進行相關操作的時候,你可以通過添加觸發器去約束用戶的操作行為.如果約束驗證成功,那么用戶的操作將會提交到數據庫當中.如果約束驗證失敗,你可以選擇回滾,并將失敗的原因告知給用戶,然后還可以把本次操作失敗的原因記錄到日志系統里面,以便為日后的分析提供一個參考依據!
在這里列舉一個AdventureWorks數據庫的一個禁止刪除[HumanResources].[Employee]表數據的例子:
CREATE TRIGGER [HumanResources].[dEmployee] ON [HumanResources].[Employee] INSTEAD OF DELETE --優先于DML觸發器執行 NOT FOR REPLICATION --在復制代理執行插入、更新或刪除操作時,不進行觸發器約束 AS BEGIN DECLARE @Count int; SET @Count = @@ROWCOUNT; IF @Count = 0 RETURN; SET NOCOUNT ON; BEGIN RAISERROR( N'Employees cannot be deleted. They can only be marked as not current.', -- Message 10, -- Severity. 1 -- State. ); IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; END END; END;
INSTEAD OF DELETE觸發器運行效果:

解釋一下ROLLBACK TRANSACTION的作用,如果我把
ROLLBACK TRANSACTION;
END
注視掉的話.~會出現以下的情況

可以看到.~雖然顯示了DELETE受影響的行數,但是我后面的一個SELECT COUNT(*)顯示數據表內的記錄仍然存在,并沒有被刪除.所以加上這三行制造一個偽錯誤提示給客戶端是一個正確的決定.
II.如何使用觸發器
1.觸發器的定義
CREATE TRIGGER [ schema_name . ]trigger_name
ON { table | view }
[ WITH <dml_trigger_option> [ ,...n ] ]
{ FOR | AFTER | INSTEAD OF }
{ [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] }
[ WITH APPEND ]
[ NOT FOR REPLICATION ]
AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME <method specifier [ ; ] > }
<dml_trigger_option> ::=
[ ENCRYPTION ]
[ EXECUTE AS Clause ]
<method_specifier> ::=
assembly_name.class_name.method_name
注意:如果同時定義了INSTEAD OF { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] } 和 FOR | AFTER { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] }的話你必須要在INSTEAD OF內使用DML語句才會使FOR|AFTER觸發器生效
觸發器內可以已INSERTED, DELETED這兩個表名去訪問將要插入,或被刪除的數據信息
另外:不能為 DDL 或登錄觸發器指定 INSTEAD OF。對于表或視圖,每個 INSERT、UPDATE 或 DELETE 語句最多可定義一個 INSTEAD OF 觸發器
更多詳細資料可以參閱: Technet
2.觸發器的種類
A:DML觸發器
功能:可以對insert, update, delete語句進行觸發檢查按照一定條件執行回滾操作,避免不符合業務要求的數據進入到數據表當中,使用INSERTED, DELETED去訪問將要被添加,刪除的數據.
例2.1-禁止刪除DatabaseLog表中的記錄
CREATE TRIGGER dDatabaseLog ON DatabaseLog FOR DELETE AS BEGIN RAISERROR('不允許刪除DatabaseLog表內的數據', 11, 1) ROLLBACK END
執行效果

B:DDL觸發器
功能:可以對任何DDL語句進行觸發檢測并按照設定好的規則進行回滾操作.
例2.2-禁止當前數據庫執行DROP TABLE, ALTER TABLE:
CREATE TRIGGER [safety] ON DATABASE FOR DROP_TABLE, ALTER_TABLE AS BEGIN RAISERROR('目前數據庫已禁止刪除,修改表', 11, 1) ROLLBACK; END
最終效果

3.INSTEAD OF 與 FOR|AFTER觸發器
INSTEAD OF 與 FOR|AFTER觸發器同屬DML觸發器,在本章的第一小節 [觸發器的定義] 當中我們已經得知,只能為INSERT|UPDATE|DELETE添加一個INSTEAD OF觸發器,而FOR|AFTER觸發器則是可以定義多個.下面我將會用一個小示例去演示INSTEAD OF觸發器優于FOR|AFTER觸發器的證明.及兩者的區別.
例3-1:
先定義一個不允許刪除DatabaseLog.DatabaseLogID < 1500的INSTEAD OF DELETE觸發器,然后在定義一個不允許刪除DatabaseLog.DatabaseLogID < 1700的FOR DELETE觸發器然后看他們被觸發的情況.請注意 [DELETED] 的位置
注意:先把之前定義的FOR DELETE觸發器dDatabaseLog刪掉. DROP TRIGGER dDatabaseLog
CREATE TRIGGER [dbo].[dDatabaseLog_1] ON [dbo].[DatabaseLog] INSTEAD OF DELETE AS BEGIN IF EXISTS( SELECT * FROM DELETED WHERE DatabaseLogID < 1500 ) BEGIN --這里并不會調用FOR DELETE觸發器,因為這個觸發器是INSTEAD OF RAISERROR('INSTEAD OF觸發器禁止刪除DatabaseLogID < 1500的記錄', 11, 1) IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; END END ELSE BEGIN --這里開始才調用FOR DELETE觸發器 DELETE FROM DatabaseLog WHERE DatabaseLogID IN ( SELECT DatabaseLogID FROM DELETED ) END END
CREATE TRIGGER [dbo].[dDatabaseLog_2] ON [dbo].[DatabaseLog] FOR DELETE AS BEGIN --如果進入到這里的話,已證明INSTEAD OF DELETE觸發器已通過約束檢測 IF EXISTS( SELECT * FROM DELETED WHERE DatabaseLogID < 1700 ) BEGIN RAISERROR( 'FOR觸發器禁止刪除DatabaseLogID < 1700的記錄', 11, 1 ) --此處的回滾操作是由FOR DELETE發出 ROLLBACK END END
從上述腳本我們可以看到在INSTEAD OF DELETE觸發器中并不需要去調用ROLLBACK去回滾操作,因為這是INSTEAD OF觸發器的一個特性.
TechNet上介紹INSTEAD OF觸發器的定義為:INSTEAD OF 指定執行 DML 觸發器而不是觸發 SQL 語句,因此,其優先級高于觸發語句的操作.
上述示例的運行效果
當DatabaseLogID < 1500條件成立時,INSTEAD OF DELETE沒有調用任何DELETE FROM語句,所以FOR DELETE觸發器不會觸發.

而當我刪除DatabaseLogID > 1501的記錄時,INSTEAD OF DELETE觸發器已經通過檢測,然后去提交DELETE FROM去調用FOR DELETE觸發器.
最終FOR DELETE因滿足約束條件DatabaseLogID < 1700而回滾了刪除操作.
III:查詢一個數據庫內的到底定義了多少個觸發器?他們所屬的表對象是誰?
1.下面我將會貼出一段T-SQL示例腳本,它的功能為顯示出當前數據庫內所有的觸發器信息
--切換數據庫 --use AdventureWorks WITH cte_AllTriggers AS ( SELECT o.name TableName, --觸發器所屬的對象(表 | 視圖) o.type_desc TableType, --描述此觸發器所屬的對象是表還是視圖 t.name TriggerName, --觸發器的名稱 t.object_id, --觸發器的object_id t.parent_class_desc, --觸發器是DML觸發器還是DDL觸發器 t.type_desc, --觸發器是SQL觸發器還是CLR觸發器 t.create_date, --觸發器的創建日期 t.modify_date, --上次使用 ALTER 語句修改觸發器的日期 t.is_ms_shipped, --是否為內部 SQL Server 組件代表用戶創建的觸發器 t.is_disabled, --觸發器是否被禁用 t.is_not_for_replication, --觸發器是否作為 NOT FOR REPLICATION 創建的 t.is_instead_of_trigger --1 = INSTEAD OF 觸發器, 0 = AFTER 觸發器 FROM sys.triggers t INNER JOIN sys.objects o ON t.parent_id = o.object_id UNION ALL SELECT NULL TableName, NULL TableType, t.name TriggerName, t.object_id, t.parent_class_desc, t.type_desc, t.create_date, t.modify_date, t.is_ms_shipped, t.is_disabled, t.is_not_for_replication, t.is_instead_of_trigger FROM sys.triggers t WHERE t.parent_id = 0 ) SELECT * FROM cte_AllTriggers ORDER BY TableName, TriggerName
執行效果如下:

在看到了上圖的運行后果以后,我發現AdventureWorks數據庫的觸發器定義使用了一個很不錯的命名約定
將Insert觸發器使用以小寫字母i開頭以作識別
將Update觸發器使用以小寫字母u開頭以做識別
將Delete觸發器使用以小寫字母d開頭以做識別
注意行號為1, 2的觸發器是DDL觸發器所以他們的表對象為NULL.
OK,介紹完怎么查看某個數據庫內所有的觸發器后,接下來!我將為你介紹AdventureWorks中觸發器的用途.
IV:DML觸發器在AdventureWorks中的應用場景
在這里并不會把12個原有的觸發器都一一講完,僅僅挑其中2個進行講解.
1.dEmployee:第一章第2節已經介紹
2.iuIndividual:位于Sales架構的Individual表.這是一個帶插入兼更新為一體的觸發器.
首先介紹一下表
Sales.Individual --客戶的統計數據表
{
CustomerID, --客戶Id
ContactID, --聯系人Id
Demographics, --統計
ModifiedDate --上次修改日期
}
Sales.Store --客戶和經銷商表
{
CustomerID, --客戶Id
Name, --商店的名稱
SalesPersonID, --Adventure Works 銷售代表的Id
Demographics, --商店的統計信息
rowguid, --用于合并數據庫
ModifiedDate --上次修改日期
}
下面的這個觸發器大概可以理解為插入或更新的表Sales.Individual的CustomerID列存在于[Sales].[Store].[CustomerID]時回滾.
當成功更新/插入列[CustomerID],[Demographics]時,根據設定好的條件更新[Sales].[Individual].[Demographics]列.
ALTER TRIGGER [Sales].[iuIndividual] ON [Sales].[Individual] --該觸發器是給[Sales].[Individual]定義的 AFTER INSERT, UPDATE --是AFTER INSERT觸發器的同時,也是AFTERC觸發器 NOT FOR REPLICATION AS BEGIN DECLARE @Count int; SET @Count = @@ROWCOUNT; IF @Count = 0 RETURN; --如果插入刪除所影響的行為0時退出 SET NOCOUNT ON; IF EXISTS ( SELECT * FROM inserted INNER JOIN [Sales].[Store] ON inserted.[CustomerID] = [Sales].[Store].[CustomerID] ) BEGIN IF @@TRANCOUNT > 0 BEGIN --當插入[Sales].[Individual]表客戶Id已存在于[Sales].[Store]表時回滾 ROLLBACK TRANSACTION; END END; IF UPDATE([CustomerID]) OR UPDATE([Demographics]) --如果CustomerID列或Demographics列更新|插入成功 BEGIN --根據條件更新Demographics列 UPDATE [Sales].[Individual] SET [Sales].[Individual].[Demographics] = N'<IndividualSurvey xmlns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/IndividualSurvey"> <TotalPurchaseYTD>0.00</TotalPurchaseYTD> </IndividualSurvey>' FROM inserted --連接到插入或更新的數據表 WHERE [Sales].[Individual].[CustomerID] = inserted.[CustomerID] AND inserted.[Demographics] IS NULL; --將要更新或插入的Demographics為空時 --根據條件插入一個XML節點 UPDATE [Sales].[Individual] SET [Demographics].modify( N'declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/IndividualSurvey"; insert <TotalPurchaseYTD>0.00</TotalPurchaseYTD> as first into (/IndividualSurvey)[1]') FROM inserted WHERE [Sales].[Individual].[CustomerID] = inserted.[CustomerID] AND inserted.[Demographics] IS NOT NULL AND inserted.[Demographics].exist(N'declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/IndividualSurvey"; /IndividualSurvey/TotalPurchaseYTD') <> 1; END; END;
我對AdventureWorks不太熟悉,需要學習研究的朋友們請自己上網搜索相關資料.
3.iWorkOrder:位于[Production].[WorkOrder]表的AFTER插入觸發器
此觸發器用于先嘗試把將要插入到表[Production].[WorkOrder]的數據先插入到[Production].[TransactionHistory]表中,如果[Production].[TransactionHistory]插入完成.那么將提交事務,否則顯示錯誤信息及回滾,并將錯誤寫入日志表內.
ALTER TRIGGER [Production].[iWorkOrder] ON [Production].[WorkOrder] AFTER INSERT --AFTER插入觸發器 AS BEGIN DECLARE @Count int; SET @Count = @@ROWCOUNT; IF @Count = 0 RETURN; --插入的行為0時退出 SET NOCOUNT ON; BEGIN TRY --往工作訂單表(TransactionHistory)插入生產工作訂單(WorkOrder)記錄 INSERT INTO [Production].[TransactionHistory] ( [ProductID] ,[ReferenceOrderID] ,[TransactionType] ,[TransactionDate] ,[Quantity] ,[ActualCost] ) SELECT inserted.[ProductID] ,inserted.[WorkOrderID] ,'W' --代表工作訂單 ,GETDATE() ,inserted.[OrderQty] ,0 FROM inserted; END TRY BEGIN CATCH --在Message窗口中輸出錯誤信息 EXECUTE [dbo].[uspPrintError]; IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; --事務數 > 0時回滾 END --記錄錯誤信息進日志 EXECUTE [dbo].[uspLogError]; END CATCH; END;
V:觸發器的弊端
學習了上面4個章節的內容后,大家都可以知道了..當你往數據庫內插入,修改,刪除數據時.在原來沒有觸發器的情況下時也僅僅需要做一下外鍵主鍵的完整性約束檢查及CHECK約束檢查.而在有了觸發器的情況又會在原有的基礎上再多跑一些T-SQL語句.所以觸發器設計得是否合理對于一個數據庫系統性能極為重要.在觸發器內部需要注意鎖的問題!
http://v.youku.com/v_show/id_XMjg5NDQ3MzUy.html (祈禱, 6++月24--)![]()
本文到此結束!



浙公網安備 33010602011771號