2009-12-26 72 views
26

我需要在我的SQL Server 2005数据库的两个表上实现更改跟踪。我需要审核添加,删除和更新(详细说明更新内容)。我打算使用触发器来做到这一点,但在Google上搜索后,我发现这样做很不容易,我想避免这种情况。在SQL Server中创建审计触发器

任何人都可以发布一个更新触发器的例子,以成功和优雅的方式完成这一点吗?我希望有一个审计表,以结束与结构如下:

  • ID
  • LOGDATE
  • 表名
  • TRANSACTIONTYPE(更新/插入/删除)
  • 的recordId
  • 字段名
  • OldValue
  • NewValue

...但我很乐意提供建议。

谢谢!

+0

结果的选择性结果的结果按照下面的链接:http://stackoverflow.com/questions/1906753/sql-statement-from-dml-trigger – Paresh

+0

我发现一个存储过程,将生成插入,更新,删除触发器和审计表。 http://www.codeproject.com/Articles/21068/Audit-Trail-Generator-for-Microsoft-SQL – guanome

回答

31

我只是想调出几点:

你不能有一个过程来跟踪所有的表使用代码生成器,你将需要生成相似但不同的触发器在每个被跟踪的桌子上。这种工作最适合自动生成代码。在你的地方,我会使用XSLT转换来从XML生成代码,并且可以从元数据自动生成XML。这使您可以在每次更改审计逻辑/结构或添加/更改目标表时通过重新生成触发器来轻松维护触发器。

考虑产能规划审计。跟踪所有值更改的审计表是迄今为止数据库中最大的表:它将包含当前数据的所有当前数据和当前数据的所有历史记录。这样的表将数据库大小增加2-3个数量级(x10,x100)。而审计表将很快成为一切的瓶颈:

  • 每一个DML操作都需要在审计表锁
  • 所有管理和维护操作必须适应由于数据库的大小审计

考虑到架构变化。名为'Foo'的表可能被删除,并且稍后可能会创建一个名为'Foo'的不同表。审计线索必须能够区分两个不同的对象。最好使用slow changing dimension的方法。

考虑是否需要高效删除审计记录。当您的应用程序主题策略规定的保留期限到期时,您需要能够删除到期的审计记录。现在看起来似乎不是什么大事,但5年后,当第一批记录到期时,审计表已经增长到9.5TB,这可能是一个问题。

考虑需要查询审核。审计表结构必须准备好对审计查询作出有效回应。如果你的审计不能被查询,那么它就没有价值。查询将完全由您的需求驱动,只有您知道这些查询,但是大多数审计记录是按时间间隔查询的(“昨天晚上7点到8点之间发生了什么变化?”),按对象查询('此记录中发生了什么变化表?')还是由作者('Bob在数据库中做了什么修改?')。

+1

伟大的输入,帮助我更好地计划。也想分享这种链接,你这样做是在说什么:http://weblogs.asp.net/jgalloway/archive/2008/01/27/adding-simple-trigger-based-auditing-to-your- sql-server-database.aspx – hitec

+0

codeproject上有一个sql文件,用于生成审计触发器和审计表。只需在数据库上运行存储过程并传入表名,然后创建插入,更新,删除触发器和审计表。 http://www.codeproject.com/Articles/21068/Audit-Trail-Generator-for-Microsoft-SQL – guanome

13

有没有通用的方式来做到这一点,你想要的。最终,你最终会为每个表编写大量代码。更何况,如果你需要比较每一列的变化,那么它可能会变得迟缓。

此外,您可能会同时更新多行意味着您需要打开游标以遍历所有记录。

我会这样做的方式将使用与正在跟踪的表相同的结构,稍后将其转换为未显示哪些列实际发生了更改。我还会跟踪实际进行更改的会话。这假定你在被跟踪的表中有主键。

所以给定一个表像这样

CREATE TABLE TestTable 
(ID INT NOT NULL CONSTRAINT PK_TEST_TABLE PRIMARY KEY, 
Name1 NVARCHAR(40) NOT NULL, 
Name2 NVARCHAR(40)) 

我会在审核schmea创建审核表是这样的。

CREATE TABLE Audit.TestTable 
(SessionID UNIQUEIDENTIFER NOT NULL, 
ID INT NOT NULL, 
Name1 NVARCHAR(40) NOT NULL, 
Name2 NVARCHAR(40), 
Action NVARCHAR(10) NOT NULL CONSTRAINT CK_ACTION CHECK(Action In 'Deleted','Updated'), 
RowType NVARCHAR(10) NOT NULL CONSTRAINT CK_ROWTYPE CHECK (RowType in 'New','Old','Deleted'), 
ChangedDate DATETIME NOT NULL Default GETDATE(), 
ChangedBy SYSNHAME NOT NULL DEFAULT USER_NAME()) 

而且这样

CREATE Trigger UpdateTestTable ON DBO.TestTable FOR UPDATE AS 
BEGIN 
    SET NOCOUNT ON 
    DECLARE @SessionID UNIQUEIDENTIFER 
    SET @SessionID = NEWID() 
    INSERT Audit.TestTable(Id,Name1,Name2,Action,RowType,SessionID) 
    SELECT ID,name1,Name2,'Updated','Old',@SessionID FROM Deleted 

    INSERT Audit.TestTable(Id,Name1,Name2,Action,RowType,SessionID) 
    SELECT ID,name1,Name2,'Updated','New',@SessionID FROM Inserted 

END 

用于更新触发此运行速度相当快。在报告期间,您只需根据sessionID和主键联接行并生成报告。或者,您可以有一个批处理作业,该作业定期遍历审核表中的所有表并准备显示更改的名称 - 值对。

HTH

+0

参见http://weblogs.asp.net/jgalloway/archive/2008/01/27/adding-simple -trigger-based-auditing-to-your-sql-server-database.aspx为通用的方式。 –

+0

近5年后称重...这大致是我要采取的方法,虽然不是每次更改都在Audit表中创建两条记录,但我将创建一个名为格式为“OldName1”的列, ,'OldName2'和'NewName1','NewName2'。这种变化不应该对审计表大小产生任何影响,查询应该同样简单,并且由于需要单个INSERT命令,可能会提高性能。不知道是否有明显的缺点? –

+0

将单行插入交流台不是更好吗?原始表格中的值将是当前(又名“新”)值。从技术上讲,插入只需要你想记录的附加数据(例如用户标识和时间标记)。对于更新和删除,记录修改前的值。 – Justin

1

Mike,我们正在使用www.auditdatabase.com工具,这个免费工具生成审计触发器,它适用于SQL Server 2008和2005以及2000年。它是一个复杂和疯狂的工具,允许为表格自定义审计触发器。

另一个很好的工具是顶点的SQL审计

+4

域名www.auditdatabase.com不可用。 –

+1

@MichaelFreidgeim,或者说,它是可用的,如果你想购买它。 –

-10

有一个通用的方法来做到这一点。

CREATE TABLE [dbo].[Audit](
    [TYPE] [CHAR](1) NULL, 
    [TableName] [VARCHAR](128) NULL, 
    [PK] [VARCHAR](1000) NULL, 
    [FieldName] [VARCHAR](128) NULL, 
    [OldValue] [VARCHAR](1000) NULL, 
    [NewValue] [VARCHAR](1000) NULL, 
    [UpdateDate] [datetime] NULL, 
    [UserName] [VARCHAR](128) NULL 
) ON [PRIMARY] 
19

我们正在使用ApexSQL Audit生成审计触发器和下面是通过此工具使用的数据结构。如果您不打算购买第三方解决方案,则可以在试用模式下安装此工具,查看它们如何实施触发器和存储,然后为自己创建类似的东西。

我没有仔细研究这些表的工作方式,但希望这会让你开始。

enter image description here

0

我会扔在我的方法和建议的组合。

我有一个非常类似的表格,您提出的设计,我曾经在SQL 2005(现在是2008年)数据库上使用了过去七年。

我添加插入,更新和删除触发选定的表,然后检查选定的字段的更改。当时它很简单,运作良好。

这里是我发现这种方法的问题:

  1. 审核表旧/新值字段必须是VARCHAR(MAX)类型,以便能够处理所有可能被审计的不同值:int,bool,decimal,float,varchar等全部必须适合

  2. 检查每个字段的代码是繁琐写维护。它也很容易错过(比如将空字段更改为一个没有被捕获的值,因为NULL!=值为NULL)。

  3. 删除记录:您如何记录这些?所有字段?变得复杂

我的未来愿景是使用一些SQL-CLR代码编写执行和检查表的元数据的通用扳机,看看要审计的内容。其次,新/旧值将被转换到XML字段和记录的整个对象:这导致更多的数据,但删除有一个完整的记录。网上有几篇关于XML审计触发器的文章

0
CREATE TRIGGER TriggerName 
ON TableName 
FOR INSERT, UPDATE, DELETE AS 
BEGIN 
SET NOCOUNT ON 

DECLARE @ExecStr varchar(50), @Qry nvarchar(255) 

CREATE TABLE #inputbuffer 
(
    EventType nvarchar(30), 
    Parameters int, 
    EventInfo nvarchar(255) 
) 

SET @ExecStr = 'DBCC INPUTBUFFER(' + STR(@@SPID) + ')' 

INSERT INTO #inputbuffer 
EXEC (@ExecStr) 

SET @Qry = (SELECT EventInfo FROM #inputbuffer) 

SELECT @Qry AS 'Query that fired the trigger', 
SYSTEM_USER as LoginName, 
USER AS UserName, 
CURRENT_TIMESTAMP AS CurrentTime 
END 
+0

你可以改进格式? –

+2

还是提供任何解释? –

+0

这里是原始来源:http://vyaskn.tripod.com/tracking_sql_statements_by_triggers.htm –

0

我终于找到了一个通用的解决方案,它不需要动态sql和日志更改所有列。

如果表更改,则不需要更改触发器。

这是审计日志:

CREATE TABLE [dbo].[Audit](
    [ID] [bigint] IDENTITY(1,1) NOT NULL, 
    [Type] [char](1) COLLATE Latin1_General_CI_AS NULL, 
    [TableName] [nvarchar](128) COLLATE Latin1_General_CI_AS NULL, 
    [PK] [int] NULL, 
    [FieldName] [nvarchar](128) COLLATE Latin1_General_CI_AS NULL, 
    [OldValue] [nvarchar](max) COLLATE Latin1_General_CI_AS NULL, 
    [NewValue] [nvarchar](max) COLLATE Latin1_General_CI_AS NULL, 
    [UpdateDate] [datetime] NULL, 
    [Username] [nvarchar](8) COLLATE Latin1_General_CI_AS NULL, 
CONSTRAINT [PK_AuditB] 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] TEXTIMAGE_ON [PRIMARY] 

这是一个表中的触发器:

INSERT INTO ILSe.dbo.Audit ([Type], TableName, PK, FieldName, OldValue, NewValue, Username) 
     SELECT 
      CASE WHEN NOT EXISTS (SELECT ID FROM deleted WHERE ID = ISNULL(ins.PK,del.PK)) THEN 'I' 
       WHEN NOT EXISTS (SELECT ID FROM inserted WHERE ID = ISNULL(ins.PK,del.PK)) THEN 'D' 
        ELSE 'U' END as [Type], 
      'AGB' as TableName, 
      ISNULL(ins.PK,del.PK) as PK, 
      ISNULL(ins.FieldName,del.FieldName) as FieldName, 
      del.FieldValue as OldValue, 
      ins.FieldValue as NewValue, 
      ISNULL(ins.Username,del.Username) as Username 
FROM (SELECT 
     insRowTbl.PK, 
     insRowTbl.Username, 
     attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName, 
     attr.insRow.value('.', 'nvarchar(max)') as FieldValue 
    FROM (Select 
      i.ID as PK, 
      i.LastModifiedBy as Username, 
      convert(xml, (select i.* for xml raw)) as insRowCol 
     from inserted as i 
     ) as insRowTbl 
     CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow) 
) as ins 
FULL OUTER JOIN (SELECT 
     delRowTbl.PK, 
     delRowTbl.Username, 
     attr.delRow.value('local-name(.)', 'nvarchar(128)') as FieldName, 
     attr.delRow.value('.', 'nvarchar(max)') as FieldValue 
    FROM (Select  
       d.ID as PK, 
       d.LastModifiedBy as Username, 
       convert(xml, (select d.* for xml raw)) as delRowCol 
     from deleted as d 
     ) as delRowTbl 
     CROSS APPLY delRowTbl.delRowCol.nodes('/row/@*') as attr(delRow) 
    ) as del 
      on ins.PK = del.PK and ins.FieldName = del.FieldName 
WHERE 
     isnull(ins.FieldName,del.FieldName) not in ('LastModifiedBy', 'ID', 'TimeStamp') 
and ((ins.FieldValue is null and del.FieldValue is not null) 
     or (ins.FieldValue is not null and del.FieldValue is null) 
     or (ins.FieldValue != del.FieldValue)) 

这种触发名为AGB一个表。名为AGB的表具有带名称ID的主键列和名称为LastModifiedBy的列,其中包含进行最后编辑的用户名。

触发器由两部分组成,首先它将插入和删除表的列转换为行。这里详细解释:https://stackoverflow.com/a/43799776/4160788

然后它通过主键和字段名称连接插入和删除表的行(每列一列),并为每个更改的列记录一行。它不会记录ID,TimeStamp或LastModifiedByColumn的更改。

您可以插入您自己的TableName,Columns名称。

您还可以创建以下存储过程,然后再调用这个存储过程来产生触发条件:

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[_create_audit_trigger]') AND type in (N'P', N'PC')) 
BEGIN 
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[_create_audit_trigger] AS' 
END 
ALTER PROCEDURE [dbo].[_create_audit_trigger] 
    @TableName varchar(max), 
    @IDColumnName varchar(max) = 'ID', 
    @LastModifiedByColumnName varchar(max) = 'LastModifiedBy', 
    @TimeStampColumnName varchar(max) = 'TimeStamp' 
AS 
BEGIN 

PRINT 'start ' + @TableName + ' (' + @IDColumnName + ', ' + @LastModifiedByColumnName + ', ' + @TimeStampColumnName + ')' 

/* if you have other audit trigger on this table and want to disable all triggers, enable this: 
EXEC ('ALTER TABLE ' + @TableName + ' DISABLE TRIGGER ALL')*/ 

IF EXISTS (SELECT * FROM sys.objects WHERE [type] = 'TR' AND [name] = 'tr_audit_'[email protected]) 
    EXEC ('DROP TRIGGER [dbo].tr_audit_'[email protected]) 


EXEC (' 
CREATE TRIGGER [dbo].[tr_audit_'[email protected]+'] ON [ILSe].[dbo].['[email protected]+'] FOR INSERT, UPDATE, DELETE 
AS 
BEGIN 
    SET NOCOUNT ON; 

     INSERT INTO ILSe.dbo.Audit ([Type], TableName, PK, FieldName, OldValue, NewValue, Username) 
     SELECT CASE WHEN NOT EXISTS (SELECT '[email protected]+' FROM deleted WHERE '[email protected]+' = ISNULL(ins.PK,del.PK)) THEN ''I'' WHEN NOT EXISTS (SELECT '[email protected]+' FROM inserted WHERE '[email protected]+' = ISNULL(ins.PK,del.PK)) THEN ''D'' ELSE ''U'' END as [Type], 
     '''[email protected]+''' as TableName, ISNULL(ins.PK,del.PK) as PK, ISNULL(ins.FieldName,del.FieldName) as FieldName, del.FieldValue as OldValue, ins.FieldValue as NewValue, ISNULL(ins.Username,del.Username) as Username FROM 
     (SELECT insRowTbl.PK, insRowTbl.Username, attr.insRow.value(''local-name(.)'', ''nvarchar(128)'') as FieldName, attr.insRow.value(''.'', ''nvarchar(max)'') as FieldValue FROM (Select  
        i.'[email protected]+' as PK, 
        i.'[email protected]+' as Username, 
        convert(xml, (select i.* for xml raw)) as insRowCol 
       from inserted as i) as insRowTbl 
       CROSS APPLY insRowTbl.insRowCol.nodes(''/row/@*'') as attr(insRow)) as ins 
      FULL OUTER JOIN 
     (SELECT delRowTbl.PK, delRowTbl.Username, attr.delRow.value(''local-name(.)'', ''nvarchar(128)'') as FieldName, attr.delRow.value(''.'', ''nvarchar(max)'') as FieldValue FROM (Select  
        d.'[email protected]+' as PK, 
        d.'[email protected]+' as Username, 
        convert(xml, (select d.* for xml raw)) as delRowCol 
       from deleted as d) as delRowTbl 
       CROSS APPLY delRowTbl.delRowCol.nodes(''/row/@*'') as attr(delRow)) as del on ins.PK = del.PK and ins.FieldName = del.FieldName 
    WHERE isnull(ins.FieldName,del.FieldName) not in ('''[email protected]+''', '''[email protected]+''', '''[email protected]+''') and 
    ((ins.FieldValue is null and del.FieldValue is not null) or (ins.FieldValue is not null and del.FieldValue is null) or (ins.FieldValue != del.FieldValue)) 

END 
') 

PRINT 'end ' + @TableName 

PRINT '' 

END 
0

每个表一个要监视,将需要其自己的触发。很明显,正如接受的答案中指出的那样,代码生成将是一件好事。

如果您喜欢这种方法,可能会想到使用此触发器,并分别用每个表的生成代码替换一些通用步骤。

尽管如此,我创建了一个完全通用审计触发器。观察表必须具有PK,但该PK甚至可能是多列

某些列类型(如BLOB)可能无法正常工作,但您可以轻松排除它们。

这会不会是在性能上:-D最好

说实话:这是比较厚道的锻炼......

SET NOCOUNT ON; 
GO 
CREATE TABLE AuditTest(ID UNIQUEIDENTIFIER 
         ,LogDate DATETIME 
         ,TableSchema VARCHAR(250) 
         ,TableName VARCHAR(250) 
         ,AuditType VARCHAR(250),Content XML); 
GO 

- 有些表来测试这个(故意用古怪的PK列...)

CREATE TABLE dbo.Testx(ID1 DATETIME NOT NULL 
         ,ID2 UNIQUEIDENTIFIER NOT NULL 
         ,Test1 VARCHAR(100) 
         ,Test2 DATETIME); 
--Add a two column PK 
ALTER TABLE dbo.Testx ADD CONSTRAINT PK_Test PRIMARY KEY(ID1,ID2); 

- 有些测试数据

INSERT INTO dbo.Testx(ID1,ID2,Test1,Test2) VALUES 
({d'2000-01-01'},NEWID(),'Test1',NULL) 
,({d'2000-02-01'},NEWID(),'Test2',{d'2002-02-02'}); 

- 这是当前内容

SELECT * FROM dbo.Testx; 
GO 

--The触发审计

CREATE TRIGGER [dbo].[UpdateTestTrigger] 
    ON [dbo].[Testx] 
    FOR UPDATE,INSERT,DELETE 
    AS 
    BEGIN 

     IF NOT EXISTS(SELECT 1 FROM deleted) AND NOT EXISTS(SELECT 1 FROM inserted) RETURN; 

     SET NOCOUNT ON; 
     DECLARE @tableSchema VARCHAR(250); 
     DECLARE @tableName VARCHAR(250); 
     DECLARE @AuditID UNIQUEIDENTIFIER=NEWID(); 
     DECLARE @LogDate DATETIME=GETDATE(); 

     SELECT @tableSchema = sch.name 
       ,@tableName = tb.name 
     FROM sys.triggers AS tr 
     INNER JOIN sys.tables AS tb ON tr.parent_id=tb.object_id 
     INNER JOIN sys.schemas AS sch ON tb.schema_id=sch.schema_id 
     WHERE tr.object_id = @@PROCID 

     DECLARE @tp VARCHAR(10)=CASE WHEN EXISTS(SELECT 1 FROM deleted) AND EXISTS(SELECT 1 FROM inserted) THEN 'upd' 
           ELSE CASE WHEN EXISTS(SELECT 1 FROM deleted) AND NOT EXISTS(SELECT 1 FROM inserted) THEN 'del' ELSE 'ins' END END; 

     SELECT * INTO #tmpInserted FROM inserted; 
     SELECT * INTO #tmpDeleted FROM deleted; 

     SELECT kc.ORDINAL_POSITION, kc.COLUMN_NAME 
     INTO #tmpPKColumns 
     FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc 
     INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kc ON tc.TABLE_CATALOG=kc.TABLE_CATALOG 
                  AND tc.TABLE_SCHEMA=kc.TABLE_SCHEMA 
                  AND tc.TABLE_NAME=kc.TABLE_NAME 
                  AND tc.CONSTRAINT_NAME=kc.CONSTRAINT_NAME 
                  AND tc.CONSTRAINT_TYPE='PRIMARY KEY' 
     WHERE [email protected] 
     AND [email protected] 
     ORDER BY kc.ORDINAL_POSITION; 

     DECLARE @pkCols VARCHAR(MAX)= 
     STUFF 
     (
     (
     SELECT 'UNION ALL SELECT ''' + pc.COLUMN_NAME + ''' AS [@name] , CAST(COALESCE(i.' + QUOTENAME(pc.COLUMN_NAME) + ',d.' + QUOTENAME(pc.COLUMN_NAME) + ') AS VARCHAR(MAX)) AS [@value] ' 
     FROM #tmpPKColumns AS pc 
     ORDER BY pc.ORDINAL_POSITION 
     FOR XML PATH('') 
     ),1,16,''); 

     DECLARE @pkColsCompare VARCHAR(MAX)= 
     STUFF 
     (
     (
     SELECT 'AND i.' + QUOTENAME(pc.COLUMN_NAME) + '=d.' + QUOTENAME(pc.COLUMN_NAME) 
     FROM #tmpPKColumns AS pc 
     ORDER BY pc.ORDINAL_POSITION 
     FOR XML PATH('') 
     ),1,3,''); 

     DECLARE @cols VARCHAR(MAX)= 
     STUFF 
     (
     (
     SELECT ',' + CASE WHEN @tp='upd' THEN 
       'CASE WHEN (i.[' + COLUMN_NAME + ']!=d.[' + COLUMN_NAME + '] ' + 
       'OR (i.[' + COLUMN_NAME + '] IS NULL AND d.[' + COLUMN_NAME + '] IS NOT NULL) ' + 
       'OR (i.['+ COLUMN_NAME + '] IS NOT NULL AND d.[' + COLUMN_NAME + '] IS NULL)) ' + 
       'THEN ' ELSE '' END + 
       '(SELECT ''' + COLUMN_NAME + ''' AS [@name]' + 
          CASE WHEN @tp IN ('upd','del') THEN ',ISNULL(CAST(d.[' + COLUMN_NAME + '] AS NVARCHAR(MAX)),N''##NULL##'') AS [@old]' ELSE '' END + 
          CASE WHEN @tp IN ('ins','upd') THEN ',ISNULL(CAST(i.[' + COLUMN_NAME + '] AS NVARCHAR(MAX)),N''##NULL##'') AS [@new] ' ELSE '' END + 
         ' FOR XML PATH(''Column''),TYPE) ' + CASE WHEN @tp='upd' THEN 'END' ELSE '' END 
     FROM INFORMATION_SCHEMA.COLUMNS 
     WHERE [email protected] AND [email protected] 
     FOR XML PATH('') 
     ),1,1,'' 
     ); 

     DECLARE @cmd VARCHAR(MAX)= 
     'SET LANGUAGE ENGLISH; 
     WITH ChangedColumns AS 
     (
     SELECT A.PK' + 
       ',A.PK.query(''data(/PK/Column/@value)'').value(''text()[1]'',''nvarchar(max)'') AS PKVals' + 
       ',Col.* 
     FROM #tmpInserted AS i 
     FULL OUTER JOIN #tmpDeleted AS d ON ' + @pkColsCompare + 
     ' CROSS APPLY 
     (
      SELECT ' + @cols + ' 
      FOR XML PATH(''''),TYPE 
     ) AS Col([Column]) 
     CROSS APPLY(SELECT (SELECT tbl.* FROM (SELECT ' + @pkCols + ') AS tbl FOR XML PATH(''Column''), ROOT(''PK''),TYPE)) AS A(PK) 
     ) 
     INSERT INTO AuditTest(ID,LogDate,TableSchema,TableName,AuditType,Content) 
     SELECT ''' + CAST(@AuditID AS VARCHAR(MAX)) + ''',''' + CONVERT(VARCHAR(MAX),@LogDate,126) + ''',''' + @tableSchema + ''',''' + @tableName + ''',''' + @tp + ''' 
     ,(
     SELECT ''' + @tableSchema + ''' AS [@TableSchema] 
       ,''' + @tableName + ''' AS [@TableName] 
       ,''' + @tp + ''' AS [@ActionType] 
     ,(
      SELECT ChangedColumns.PK AS [*] 
      ,(
      SELECT x.[Column] AS [*],'''' 
      FROM ChangedColumns AS x 
      WHERE x.PKVals=ChangedColumns.PKVals 
      FOR XML PATH(''Values''),TYPE 
      ) 
      FROM ChangedColumns 
      FOR XML PATH(''Row''),TYPE 
      ) 
     FOR XML PATH(''Changes'') 
     );'; 

     EXEC (@cmd); 

     DROP TABLE #tmpInserted; 
     DROP TABLE #tmpDeleted; 
    END 
    GO 

--Now让我们有一些操作测试:

UPDATE dbo.Testx SET Test1='New 1' WHERE ID1={d'2000-01-01'}; 
UPDATE dbo.Testx SET Test1='New 1',Test2={d'2000-01-01'} ; 
DELETE FROM dbo.Testx WHERE ID1={d'2000-02-01'}; 
DELETE FROM dbo.Testx WHERE ID1=GETDATE(); --no affect 
INSERT INTO dbo.Testx(ID1,ID2,Test1,Test2) VALUES 
({d'2000-03-01'},NEWID(),'Test3',{d'2001-03-03'}) 
,({d'2000-04-01'},NEWID(),'Test4',{d'2001-04-04'}) 
,({d'2000-05-01'},NEWID(),'Test5',{d'2001-05-05'}); 
UPDATE dbo.Testx SET Test2=NULL; --all rows 
DELETE FROM dbo.Testx WHERE ID1 IN ({d'2000-02-01'},{d'2000-03-01'}); 
GO 

- 检查最终状态

SELECT * FROM dbo.Testx; 
SELECT * FROM AuditTest; 
GO 

- 清理(真实数据!

DROP TABLE dbo.Testx; 
GO 
DROP TABLE dbo.AuditTest; 
GO 

插入

<Changes TableSchema="dbo" TableName="Testx" ActionType="ins"> 
    <Row> 
    <PK> 
     <Column name="ID1" value="May 1 2000 12:00AM" /> 
     <Column name="ID2" value="C2EB4D11-63F8-434E-8470-FB4A422A4ED1" /> 
    </PK> 
    <Values> 
     <Column name="ID1" new="May 1 2000 12:00AM" /> 
     <Column name="ID2" new="C2EB4D11-63F8-434E-8470-FB4A422A4ED1" /> 
     <Column name="Test1" new="Test5" /> 
     <Column name="Test2" new="May 5 2001 12:00AM" /> 
    </Values> 
    </Row> 
    <Row> 
    <PK> 
     <Column name="ID1" value="Apr 1 2000 12:00AM" /> 
     <Column name="ID2" value="28625CE7-9424-4FA6-AEDA-1E4853451655" /> 
    </PK> 
    <Values> 
     <Column name="ID1" new="Apr 1 2000 12:00AM" /> 
     <Column name="ID2" new="28625CE7-9424-4FA6-AEDA-1E4853451655" /> 
     <Column name="Test1" new="Test4" /> 
     <Column name="Test2" new="Apr 4 2001 12:00AM" /> 
    </Values> 
    </Row> 
    <Row> 
    <PK> 
     <Column name="ID1" value="Mar 1 2000 12:00AM" /> 
     <Column name="ID2" value="7AB56E6C-2ADC-4945-9D94-15BC9B3F270C" /> 
    </PK> 
    <Values> 
     <Column name="ID1" new="Mar 1 2000 12:00AM" /> 
     <Column name="ID2" new="7AB56E6C-2ADC-4945-9D94-15BC9B3F270C" /> 
     <Column name="Test1" new="Test3" /> 
     <Column name="Test2" new="Mar 3 2001 12:00AM" /> 
    </Values> 
    </Row> 
</Changes> 

更新

<Changes TableSchema="dbo" TableName="Testx" ActionType="upd"> 
    <Row> 
    <PK> 
     <Column name="ID1" value="Feb 1 2000 12:00AM" /> 
     <Column name="ID2" value="D7AB263A-EEFC-47DB-A6BB-A559FE8F2119" /> 
    </PK> 
    <Values> 
     <Column name="Test1" old="Test2" new="New 1" /> 
     <Column name="Test2" old="Feb 2 2002 12:00AM" new="Jan 1 2000 12:00AM" /> 
    </Values> 
    </Row> 
    <Row> 
    <PK> 
     <Column name="ID1" value="Jan 1 2000 12:00AM" /> 
     <Column name="ID2" value="318C0A66-8833-4F03-BCEF-7AB78C91704F" /> 
    </PK> 
    <Values> 
     <Column name="Test2" old="##NULL##" new="Jan 1 2000 12:00AM" /> 
    </Values> 
    </Row> 
</Changes> 

和删除

<Changes TableSchema="dbo" TableName="Testx" ActionType="del"> 
    <Row> 
    <PK> 
     <Column name="ID1" value="Mar 1 2000 12:00AM" /> 
     <Column name="ID2" value="7AB56E6C-2ADC-4945-9D94-15BC9B3F270C" /> 
    </PK> 
    <Values> 
     <Column name="ID1" old="Mar 1 2000 12:00AM" /> 
     <Column name="ID2" old="7AB56E6C-2ADC-4945-9D94-15BC9B3F270C" /> 
     <Column name="Test1" old="Test3" /> 
     <Column name="Test2" old="##NULL##" /> 
    </Values> 
    </Row> 
</Changes> 
相关问题