SQL SERVER 準行級觸發器
ⅰDeleted表和Inserted表
SQL SERVER只有表級和服務器級(相當)的觸發器,在很多需要使用行級觸發器的場合,很多同事都開始懷念使用Oracle的日子。但是事實上,我認爲SQL SERVER這樣做有它的理由(或難處),甚至於很多時候我會說,我們不需要精細到以行來觸發的操作,因爲我們往往要做的是處理哪些數據而不是哪條數據。不過如果您真的很需要,那麼SQL SERVER提供了相應的手段足以讓您實現類似的功能,並且應有更大的靈活性。
對於SQL SERVER,一個添修刪操作對應於確定的觸發器只會作用一次,而不管在這個操作中有多少紀錄受到了該次操作的影響,因爲它是語句級的。但這並不意味着我們需要去遍歷比較整個表纔可以得知哪些記錄發生了改變。在DML觸發器開發中,我們可以通過Deleted表和Inserted表得到激活當前觸發器的操作中那些受到影響的記錄。
對於這兩個表,我們可以這樣理解。在一個添修刪操作過程中,SQL引擎生成了這兩個臨時表。他把刪除掉的數據暫時存儲在Deleted表內,把要追加的數據存儲在Inserted表內。而對於修改來說,我們把他理解爲刪除舊記錄和加入新記錄兩個步驟,那麼就是說他在兩個表內分別存儲了目標記錄修改前後的數據。
想要檢驗這種猜測的合理性,我們不妨用下面的例子試試看。
-- 初始化
IF OBJECT_ID ('tbStudents', 'TABLE') IS NOT NULL
DROP TABLE [tbStudents]
GO
IF OBJECT_ID ('tbStudentsLog', 'TABLE') IS NOT NULL
DROP TABLE [tbStudentsLog]
GO
IF OBJECT_ID ('TR_StudentNameChanged', 'TR') IS NOT NULL
DROP TRIGGER TR_StudentNameChanged
GO
-- 學生表(包含學號和姓名兩個字段)
CREATE TABLE [dbo].[tbStudents](
[StudentID] [nchar](10) COLLATE Japanese_CI_AS NOT NULL,
[StudentName] [nchar](10) COLLATE Japanese_CI_AS NULL,
CONSTRAINT [PK_tbStudents] PRIMARY KEY CLUSTERED ( [StudentID] ASC )
)
GO
-- 學生記錄表(包含學號、現在姓名、曾用名三個字段)
CREATE TABLE [dbo].[tbStudentsLog](
[StudentID] [nchar](10) COLLATE Japanese_CI_AS NOT NULL,
[StudentNewName] [nchar](10) COLLATE Japanese_CI_AS NULL,
[StudentOldName] [nchar](10) COLLATE Japanese_CI_AS NULL,
CONSTRAINT [PK_tbStudentsLog] PRIMARY KEY CLUSTERED ( [StudentID] ASC )
)
GO
--定義[tbStudents]表被修改時的觸發器
CREATE TRIGGER TR_StudentNameChanged
ON [tbStudents]
AFTER UPDATE
AS
--將被修改學生記錄的修改前後的值存入學生記錄
INSERT [tbStudentsLog]
SELECT [Inserted].[StudentID]
, [Inserted].[StudentName] AS [StudentNewName]
, [Deleted].[StudentName] AS [StudentOldName]
FROM [Inserted]
INNER JOIN [Deleted] ON [Deleted].[StudentID] = [Inserted].[StudentID]
WHERE NOT EXISTS(
SELECT *
FROM [tbStudentsLog]
WHERE [tbStudentsLog].[StudentID] = [Inserted].[StudentID])
GO
--加入兩個學生
INSERT [dbo].[tbStudents] VALUES ('0000000001','小臭')
INSERT [dbo].[tbStudents] VALUES ('0000000002','小美')
--檢查學生和學生紀錄
SELECT * FROM [dbo].[tbStudents]
SELECT * FROM [dbo].[tbStudentsLog]
--修改一個學生的姓名
UPDATE [dbo].[tbStudents] SET [StudentName] = '大紅花' WHERE [StudentID] = '0000000001'
--驗證觸發器地執行結果
SELECT * FROM [dbo].[tbStudents]
SELECT * FROM [dbo].[tbStudentsLog]
執行的結果:
我們把修改學生的姓名的SQL文的WHERE子句刪除掉,替換成下面的語句再試試看
UPDATE [dbo].[tbStudents] SET [StudentName] = '全改掉'
執行後的結果是
ⅱ實現行級觸發器
爲了實現實現行級觸發器,我們需要動用SQL遊標,但是在觸發器內一般的做法是不建議使用遊標。因此這裏只記錄下這個思路,如果您真的要實現它,那麼我想不是多麼複雜。但是我想,一個程序員一旦應有了這種自主性,那麼它往往就不會真的就只在這個觸發器內模擬一個行級觸發的功能吧。或許有一天我會真的需要它,那時候再來補上這段代碼吧。