2011-10-06 55 views
0

我找不到一種簡單/通用的方式向註冊表中註冊某些表上已更改的列。更新審計時的SQL-Server觸發器

我想用這種方式更新之後做使用上的觸發:

首先審覈表定義的:

CREATE TABLE [Audit](
[Id] [int] IDENTITY(1,1) NOT NULL, 
[Date] [datetime] NOT NULL default GETDATE(), 
[IdTypeAudit] [int] NOT NULL, --2 for Modify 
[UserName] [varchar](50) NULL, 
[TableName] [varchar](50) NOT NULL, 
[ColumnName] [varchar](50) NULL, 
[OldData] [varchar](50) NULL, 
[NewData] [varchar](50) NULL) 

下一頁上任何表中的AFTER UPDATE觸發器:

DECLARE 
    @sql varchar(8000), 
    @col int, 
    @colcount int 

select @colcount = count(*) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' 
set @col = 1 

while(@col < @colcount) 
begin 

    set @sql= 
    'INSERT INTO Audit 
    SELECT 2, UserNameLastModif, ''MyTable'', COL_NAME(Object_id(''MyTable''), '+ convert(varchar,@col) +'), Deleted.' 
    + COL_NAME(Object_id('MyTable'), @col) + ', Inserted.' + COL_NAME(Object_id('MyTable'), @col) + ' 
    FROM Inserted LEFT JOIN Deleted ON Inserted.[MyTableId] = Deleted.[MyTableId] 
    WHERE COALESCE(Deleted.' + COL_NAME(Object_id('MyTable'), @col) + ', '''') <> COALESCE(Inserted.' + COL_NAME(Object_id('MyTable'), @col) + ', '''')' 

    --UserNameLastModif is an optional column on MyTable 
    exec(@sql) 
    set @col = @col + 1 

end 

的問題

  1. 插入或刪除丟失的背景下,當我使用exec功能
  2. 似乎colnumber它並不總是一個相關的數字,似乎如果你創建一個表與20列,你刪除一個並創建另一箇中,最後一個擁有數> @colcount

我一直在尋找過網所有的解決方案,但我couln't弄清楚

任何想法?

謝謝!

回答

1

這突出了結構選擇帶來的更大問題。嘗試寫一個基於集合的解決方案。刪除循環和動態SQL並編寫插入審計行的單個語句。有可能讓它更容易考慮不同的表格佈局,例如將所有列保留在1行而不是分割它們。

在SQL 2000中使用syscolumns。在SQL 2005+中使用sys.columns。即

SELECT column_id FROM sys.columns WHERE object_id = OBJECT_ID(DB_NAME()+'.dbo.Table'); 
+0

我瞭解您所說的解決方案,但這意味着我需要爲每個要審覈的表創建一個審覈表,因此您需要在數據庫中搜索更改的額外複雜性。感謝您的幫助!也許我會做你建議的。 – Santiago

+1

@Sanitago,你確實需要每個表的審計表,否則你將會遇到鎖定問題。 – HLGEM

+0

@HLGEM:很好的一點。我寫的最近一個觸發器使用服務代理(不是用於審計,但它是一個可能的解決方案)。它將異步排隊,直到鎖被釋放。雖然如果你要付出很多努力,爲什麼不分開表格。他們也會更快地查詢。 –

1

@Santiago:如果你仍然想用動態SQL編寫它,你應該先準備好所有的語句然後執行它們。 8000個字符可能不足以滿足所有語句的要求。一個好的解決方案是使用表來存儲它們。

IF NOT OBJECT_ID('tempdb..#stmt') IS NULL 
    DROP TABLE #stmt; 
CREATE TABLE #stmt (ID int NOT NULL IDENTITY(1,1), SQL varchar(8000) NOT NULL); 

然後用INSERT INTO #stmt (SQL) VALUES (@sql);

替換行exec(@sql)然後exec的每一行。

WHILE EXISTS (SELECT TOP 1 * FROM #stmt) 
BEGIN 
    BEGIN TRANSACTION; 
     EXEC (SELECT TOP 1 SQL FROM #stmt ORDER BY ID); 
     DELETE FROM #stmt WHERE ID = (SELECT MIN(ID) FROM #stmt); 
    COMMIT TRANSACTION; 
END 

記得使用sys.columns作爲列循環(我假設你使用SQL 2005/2008)。

SET @col = 0; 
WHILE EXISTS (SELECT TOP 1 * FROM sys.columns WHERE object_id = OBJECT_ID('MyTable') AND column_id > @col) 
BEGIN 
    SELECT TOP 1 @col = column_id FROM sys.columns 
    WHERE object_id = OBJECT_ID('MyTable') AND column_id > @col ORDER BY column_id ASC; 
    SET @sql .... 
    INSERT INTO #stmt .... 
END 

刪除線4 @colcount int和行進逗號。刪除信息架構選擇。

1

請勿使用任何形式的循環觸發器。不要使用動態SQl或調用存儲過程或發送電子郵件。所有這些事情在觸發器中都是不恰當的。

如果要使用動態sql,請使用它創建腳本以創建觸發器。並且爲每個想要審計的表創建一個審計表(實際上每個表都有兩個表),否則由於鎖定在「統一所有表的一個表」上,會出現性能問題。

+0

與你同意,我不想使用循環既不動態SQL,但我的問題是,我無法找到一種方式來保存更改列以非阻塞的方式...像觸發器可以做的。 – Santiago