使用 Trigger 紀錄資料表的新增、修改、刪除的行為

原文: http://www.dotblogs.com.tw/jameswu/archive/2009/07/23/9643.aspx


備份如下:

常被要求當資料表內的紀錄有被新增修改或刪除的時候,需要同步紀錄每次資料表變動的前後資料,這時候我們可以利用 Trigger 便可做到符合這個功能的需求,而且可以適用到大部份的使用情境,又因為 Trigger 本身也可以捕捉到資料庫的 DML 的事件,如 : INSERT、UPDATE 、DELETE ,我們也能捕捉在該資料列被修改之後 (AFTER DML) 才來執行 Trigger ,所以剛好滿足我們要記錄的資料表變動的行為。

Microsoft SQL Server 的 Trigger 內會包含有兩個特殊的虛擬資料表 inserted 與 deleted ; 這兩個資料表分別會擷取引發 Trigger 的資料列修改之前、後資料內容,當然它們會儲存單筆或多筆資料列更新的資料,下表說明了每個 DML 的操作內的 inserted、deleted 會包含的資料列之內容。

資料庫 DML inserted deleted
INSERT 插入的新資料內容
UPDATE 欲更新的新資料內容 更新前的舊資料內容
DELETE 被刪除前的資料內容

當有新的資料列插入該資料表時,會觸發  INSERT 事件,會複製該新增資料列的內容到虛擬資料表 inserted ,但不會存在有虛擬資料表 deleted ;
若是某筆資料列被更新了,那麼會觸發 UPDATE 事件,並且會複製欲更新的新資料內容到虛擬資料表 inserted ,同時將舊資料列內容複製到虛擬資料表 deleted ;
刪除某筆資料列的話,將觸發 DELETE 事件,此時會將刪除前的資料列內容複製到虛擬資料表 deleted ,因為沒有資料新增或更新,所以,不會存在有虛擬資料表 inserted 。

在後面附上的面的 Trigger 內容中的 6~14 行,我便利用上述的原理來判斷觸發此 Trigger 的 DML 事件為何? 所以,我們需要對於資料庫的運作原理是有進一步了解,才有辦法靈活運用的。

上面的一堆資料庫的運作原理,主要是我想在同一個 Trigger 中同時使用多個 AFTER DML 事件,因為我不想維護同一類的 Trigger 內容,當然,也得看使用的情境,如果你的 AFTER INSERT 還包含其他的運算邏輯,那建議還是將內容分開兩份 Trigger 比較恰當。

再者,之前看過很多範例 (包含我以前自己寫的) ,若要紀錄所有的歷史紀錄大部分是新增跟原始資料表相同的欄位 (Schema) ,頂多在後面新增一個資料欄位來記錄該筆是,update、insert 或 delete,這樣雖然可行,但我想到萬一原始資料表的欄位有新增、修改或刪除,那麼這個歷史資料表也要同步更新,而我們又有可能忘了同步更新,而造成某些欄位的資料內容沒有被記錄到,我考量到這個問題,且想提高此 Trigger 的重複利用性,因此,我想將該資料列內的資料內容全部存成 XML 資料格式,將此 XML 資料放在一個 XML 資料型態的欄位即可,當然這有個缺點是,當要調閱某筆歷史資料時,你可能沒辦法很簡單的就查詢到該筆記錄的內容,可能需要再使用其他方法來解析出 XML 的內容才行,這部分在稍後我也會提供一個方法技巧來做查詢的。

要將資料列轉換成 XML 格式的資料其實不難,只要利用 SQL Server 2000 以後提供的  FOR XML ,便可輕易的將資料表資料轉換成需要的任何 XML 內容格式了 ,在下方的 Trigger 內容中的 17 與 20 行便有使用到此功能。

但為了對 XML 內容的查詢方便,我選擇了使用 FOR XML RAW, ELEMENTS, ROOT 來產出我要的格式。

下方是我實作的一個範例,首先要建立一個新的 LOG 資料表來存放紀錄 :

CREATE TABLE [dbo].[LOG](
     [id] [uniqueidentifier] NOT NULL,
     [dbname] [varchar](25) NULL,
     [recoder] [xml] NULL,
     [istype] [tinyint] NULL,
     [updatedate] [datetime] NULL,
  CONSTRAINT [PK_LOG] PRIMARY KEY CLUSTERED([id] ASC)
  WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
 ) ON [PRIMARY]
 GO
 ALTER TABLE [dbo].[LOG] ADD  CONSTRAINT [DF_LOG_id]  DEFAULT (newid()) FOR [id]
 GO
 ALTER TABLE [dbo].[LOG] ADD  CONSTRAINT [DF_LOG_updatedate]  DEFAULT (getdate()) FOR [updatedate]
 GO

再來我們使用在 NorthWind 資料庫的 Customers 資料表內建立一個新的 Trigger ,此 Trigger 會在 Customers 資料表有被修改後觸發 :

CREATE TRIGGER [dbo].[trCustomers_UPDATE_INSERT_DELETE] ON [dbo].[Customers] AFTER UPDATE,INSERT,DELETE
AS
BEGIN
   DECLARE @record XML
   DECLARE @IsType TINYINT
   SET @IsType=''
   IF EXISTS(SELECT 1 FROM inserted) AND NOT EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 1    --Insert

   IF EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 2    --Update

   IF NOT EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 3    --Delete

   --只想記錄 UPDATE 事件更新資料前的舊資料
   IF (@IsType = 1)
       SET @record=(SELECT * FROM inserted FOR XML RAW('Customers'), ELEMENTS,ROOT)
   ELSE
       SET @record=(SELECT * FROM deleted FOR XML RAW('Customers'), ELEMENTS,ROOT)

   IF (@IsType <>'')
   BEGIN
       INSERT INTO [log]([dbname],[recoder],[istype])VALUES('Customers',@record  ,@IsType)
   END
END

上面的就已經做到我們要的儲存修改紀錄的功能了,不過美中不足的是這樣的資料結構不是那麼的方便查詢,必須要再利用 OPENXML 將 XML 資料取出來。

假設要追蹤某一筆記錄,你可以先使用 SQL 指令查詢到該筆記錄後,將 Log 資料表的 recoder 欄位的 XML 資料丟給 OPENXML 處理即可,如下方法 : (其實這方法可以應用在 Master and Detail view 情境中, 也就是你將之前的紀錄先以類似 GridView 的列表顯示所有紀錄後, 再使用 DetailsView 等將詳細的紀錄顯示出來)

DECLARE @xmldoc XML
SELECT @xmldoc = recoder FROM Log WHERE [id]='AF35C6FF-E36E-41D5-9158-911931ABDB19'

DECLARE @hDoc AS INT
EXEC sp_xml_preparedocument @hDoc OUTPUT, @xmldoc
SELECT *
FROM OPENXML(@hDoc, '/root/Customers', 2)
WITH ([CustomerID] [nchar](5),
    [CompanyName] [nvarchar](40),
    [ContactName] [nvarchar](30),
    [ContactTitle] [nvarchar](30),
    [Address] [nvarchar](60),
    [City] [nvarchar](15),
    [Region] [nvarchar](15),
    [PostalCode] [nvarchar](10),
    [Country] [nvarchar](15),
    [Phone] [nvarchar](24),
    [Fax] [nvarchar](24))

EXEC sp_xml_removedocument @hDoc

單眼皮女孩 之四:利用

話說這篇文章隔了三年,還真有夠久的…
只為了給這故事一個ENDING,本文再啟!!

不知從何時開始,FB上再也看不到她了,
是被封鎖了嗎? 還是被砍帳號?
一股腦的亂猜….也隨著時光流逝,
慢慢習慣沒有她的世界…..

「是因為我的問題嗎?」 我一直有著疑惑…

這事我父母、介紹人沒有再提及過,只是…….
後續還有發展…只是消息被隱藏了起來

據說因我的出現,前男友(?)有了危機感…
沒多久就下聘了…

故事結束!!

到頭我只是個被利用的角色!! 可笑~可笑~

續章 陽光沙攤

某天LINE上的討論,原來總幹事的朋友H是約小巨蛋溜冰的,

但因有人怕摔、有人不能摔、有的則怕冷,最後作罷改去福隆看沙雕,

 

行程確定,「早上福隆、下午金瓜石、晚上九份」,而我呢也好不容易向家裡借到了車子,

就和麻吉兩台車、七個人。

 

這七個人,有我、麻吉、總幹事、總幹事的好友H、新朋友(總幹事表妹)、

和我同公司二次的同事W、再加上麻吉的堂妹,

像老鼠會似的一個拉著一個加入,就這樣本團成行了,呀比!!

 

出發日,一如預料中的是大太陽的天氣,我果然是陽光男孩!! (應該是陽光老男人還差不多)

 

隱型眼鏡OK,太陽眼鏡也OK,沿著64快速道路,不到20分便抵達集合點,待速在路邊等人的我,

相對於外面的大太陽,車裡冷氣開到最強,希望給怕熱的女生們一個涼爽的空間,

或許新朋友就會坐我這台車? 心中打著這鬼主意。

 

在LINE留言,「我已經到囉!! 車號1186」

 

對了,出發前有發LINE請新朋友代買麥當當的早餐,心中滿是期待,是怎樣的女生呢?

 

看著LINE討論串的回應,等著等著,集合時間也到了,真不準時的一群人…

 

LINE,總幹事:「我表妹已經到了,我叫他先上車喔!! 」

「好好幫我照顧他喔!!」總幹事特別交待。

我心裡的PS,是女生哥哥我都會好好照顧呀!! 嘿嘿嘿~~~

 

咚咚的敲窗聲,她是雪莉,總幹事的堂妹,令我SHOCK的是,她和LIINE的照片完全不同!!

照片果然是會騙人的。

 

雪莉一頭過肩長捲髮,特別是她的笑容,我超愛,比起LINE上裝萌齊瀏海照,我覺得本人比較正。

 

至於有人吐她很黑,第一眼倒沒有注意,黑反而比較健康,不是嗎? 只是後來才知道她不是運動型的妹。

 

坐上副駕駛座的她,拿著麥當當早餐給我,SOCIAL時間。

 

「你是做什麼工作的呀?」我問

 

「倉管呀!! 就是負責貨物進出」她說

 

「那不是很累,要搬貨?」我說

 

「不用呀!! 只要負責點貨,在鍵盤上打打字就好」她回

她接著笑著說「老闆進健康產品回來,量一多時要幫忙裝箱」

 

「老闆省這個錢呀」我追問

 

「對呀!! 若一個要4角,一箱十個的話不就要4元啦」她說

 

「老闆就是這樣…哈哈哈!!」心有戚戚焉

 

「妳從哪過來?」下個話題開始

 

「那麼遠喔!! 果然是住最近的人會遲到」

「其他人真有夠不準時的!!」我說

 

就這麼瞎哈啦著…,而我呢? 雖然嘴巴上仍聊著天,卻因第一眼的驚豔而不敢再次直視。(人家也害羞啦!!)

 

第一次聊天,她給我的感覺是個熟了後會無所不談的人,基本是個HIGH咖!! (事實証明果然如此)

 

不久,另一台車子出現,成員也到齊囉!! (雖然他們遲到了) (哈!! 很在意這件事)

 

俗話說,三個女人聚在一起就會成為菜市場,別的地方我不知道,

至少我車上就是吵吵鬧鬧。

 

車位的安排很故意,雪莉坐副駕,總幹事或許怕不熟而太監介,

除了他自己之外,另拉了他的好朋友H上來。

 

H之於總幹事,相對於我之於麻吉,都是學生時期就認識的女性好朋友,

關於她的事說來話長,就容小弟改天讓我出個番外篇來混口飯吃。

 

車上的對話,只有爆笑可形容,戰局一面倒,總幹事完敗,

還好我是駕駛,她們不敢鬧我,若就立場來說我當然是跟雪莉一起排擠總幹事呀!! XD

 

只因總幹事誤把單位KM說成M,

才有以下的對話,

「離福隆還有31公分」

「快回火星啦!! 地球很危險的」

 

總幹事哀怨的說

「雪莉和H都是天蠍,我都被吃死死」

 

真是如此,也是第一次看見總幹事處於下坡,哈哈!!

 

路上雖有塞車,有美女陪就不覺得累,而且一次來三位,真是爽到了。

 

“女人果然是男人前進的原動力呀!!”

 

順利的在路旁小徑停到車位,啥!! 要收費,出來玩就別計較那麼多啦,小氣鬼。 (我心裡這麼OS)

 

成員集合,第一站福隆砂雕展。

 

印象中,海邊不外就是陽光、沙灘,還有比基尼……

而福隆沙雕辦了這麼多年,我還是第一次來看,沒辦法本人就是宅。

門票出奇的便宜,只要100元,和原本的入園票相同。

 

舉手「報告總幹事,我準備好了!!」

 

沙雕不是重點,妹才是一切呀!!

太陽眼鏡的好處之一就是眼睛可以偷瞄….XD

但在雪莉面前我可要乖一點,印象分數很重要地.

 

向前衝,踩著燙人的沙,這就是夏天的感覺呀!!

 

看著藍天及大海,怕矖太陽的女生們也開始怨嘆為何來海邊沒帶拖鞋?

只見麻吉帶著男生們往海裡衝,而女生們則因想先逛沙雕,跑去買拖鞋了.

男生們捲起褲管在海邊留下溼溚溚的回憶,白話說就是在站在海水裡來張大合照…

 

但天氣實在是太熱了,已從一開始能細細研究沙雕的主題,到只希望能快點繞完一圈,

而總幹事和雪莉早早就躲在遮雨棚下躲太陽了,真不虧是防曬二人組….XD

 

我們這團沒什麼特別的,就是重吃,畢竟民以食為天

中午時間一到,就有人直囔啷「放飯囉!!」

 

福隆最有名的就是便當,這就不用多說啦!!

告訴大家有名的是最接近車站的二間,只因沒有賣素的,所以選了另一間,

不用說中午時候到哪都要排隊,便當也是,便利商店也是,

好熱,熱到只有個陰影就足夠了。

 

招牌便當,豬肉、滷蛋是基本的,雖然坐位很小還擠的要死,但這一頓還是很滿足,

看E也吃光光就知道啦!!

 

放完飯就該去午睡了。

沒啦!! 要趕去下個行程,金瓜石、九份,上車出發。

SQL Server 2008 介面區組態(Surface Area Configuration,SAC):啟用 xp_cmdshell

http://sharedderrick.blogspot.tw/2009/04/sql-server-2008-surface-area.html

為了安全起見,預設在「介面區組態」內的功能,都已經停用。


xp_cmdshell (Transact-SQL)
繁衍 Windows 命令 Shell 並傳入字串中以供執行。任何輸出都會當作文字資料列來傳回。
xp_cmdshell 繁衍的 Windows 處理序擁有與 SQL Server 服務帳戶相同的安全性權限。
xp_cmdshell 會同步操作。完成 command-shell 命令時,才會將控制權傳回呼叫者。

若你確認需要啟用或是停用介面區組態上的功能,請參考以下的步驟:
使用 SQL Server Management Studio 中的「Facet」來設定啟用和停用 SQL Server 功能。
1. 使用 SQL Server Management Studio,連接到目標的 SQL Server。
2. 在 [物件總管] 中,點選目標伺服器,滑鼠右鍵,選擇「Facet」。 請參考下圖所示:
選取「Facet」01
3. 在「檢視 Facet」視窗,在右邊的「Facet」區域,選擇「介面區組態」。
4. 在右下角的「Facet屬性」區域,選擇所需要啟用或是停用的功能。
請參考下圖所示:
02_設定介面區組態

以下為使用 sp_configure 來啟用 xp_cmdshell:

--01 查詢是否已經啟用 xp_cmdshell
SELECT name N'組態選項的名稱', value N'針對這個選項所設定的值', value_in_use N'這個選項目前有效的執行值',
description N'組態選項的描述'
FROM sys.configurations
WHERE name='xp_cmdshell'

--02 設定啟用 xp_cmdshell
USE master;
GO
EXEC sp_configure 'show advanced option', '1';
GO
RECONFIGURE
GO
--
EXEC sp_configure 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE
GO

--03 再度查詢是否已經啟用 xp_cmdshell
SELECT name N'組態選項的名稱', value N'針對這個選項所設定的值', value_in_use N'這個選項目前有效的執行值',
description N'組態選項的描述'
FROM sys.configurations
WHERE name='xp_cmdshell'