审计更新时的SQL-Server触发器

时间:2011-10-06 21:39:36

标签: sql-server triggers audit-tables

我找不到一种简单/通用的方法来注册审计表,在某些表上更改了列。

我试图以这种方式在更新后使用触发器来执行此操作:

首先是审计表定义:

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. 时,Inserted和Deleted丢失了上下文
  3. 似乎它的数字并不总是相关的数字,如果您创建一个包含20列的表并且删除一个并创建另一个,则最后一个具有数字&gt; @colcount
  4. 我一直在为网络寻找解决方案,但我不知道

    任何想法?

    谢谢!

3 个答案:

答案 0 :(得分: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'); 

答案 1 :(得分: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); 

然后将行exec(@sql)替换为INSERT INTO #stmt (SQL) VALUES (@sql);

然后执行每一行。

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和正在进行的逗号。删除信息架构选择。

答案 2 :(得分:1)

不要使用任何类型的循环触发器。不要使用动态SQl或调用存储过程或发送电子邮件。所有这些事情在触发器中都是不恰当的。

如果tyou想要使用动态sql,则使用它创建脚本来创建触发器。并为您想要审计的每个表创建一个审计表(我们实际上每个表都有两个),否则由于锁定“一个表来统治所有表”而导致性能问题。