2011-10-06 54 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