0

我正在编写将记录插入Audit表中的触发器。

每当我的目标表中的数据发生变化时,触发器都会将旧值、新值更新到审计表

此外,还有一些列称为TransactionTransaction_Status

Transaction列定义事务的类型。可以是INSERTUPDATEDELETE
Transaction_Status列表示SUCCESSFAILURE

如何做到这一点?

我的触发器:

Alter Trigger TR_test
ON subscribers
FOR UPDATE
AS BEGIN
DECLARE @OldValue xml,@NewValue xml, @changedby varchar(50), @ReferenceId int
-----------------------------------------------------------------------------
SELECT @OldValue=b.username, @NewValue=a.username, 
       @ReferenceId = a.user_id, @changedby = a.modified_by
FROM inserted a, deleted b;
----------------------------------------------------------------------------- 
INSERT INTO [dbo].[audit_log]
           ([old_value],[new_value],[module],[reference_id],[transaction]
           ,[transaction_status],[stack_trace],[modified_on],[modified_by])
     VALUES
(@OldValue,@NewValue,'Subscriber',@ReferenceId,'_transaction',
'_transaction_status','_stack_trace',getdate(),555)

-----------------------------------------------------------------------------
END
4

1 回答 1

8

一旦您修复触发器以涵盖所有三个操作,

IF EXISTS (SELECT 1 FROM inserted)
BEGIN
  IF EXISTS (SELECT 1 FROM deleted)
  BEGIN
    SET @action = 'UPDATE';
  END
  ELSE
  BEGIN
    SET @action = 'INSERT';
  END
ELSE
BEGIN
  SET @action = 'DELETE';
END

另一种选择是三个单独的触发器,每个动作一个。

MERGE如果您正在使用它,请小心……或者当您迁移到 SQL Server 2008 或更高版本时,请做好准备。

编辑

我认为你可能追求的是一个INSTEAD OF触发器(多么讽刺)。这是一个例子。让我们考虑一个非常简单的表,其中包含一个 PK 列和一个唯一列:

CREATE TABLE dbo.foobar(id INT PRIMARY KEY, x CHAR(1) UNIQUE);
GO

还有一个简单的日志表来捕捉活动:

CREATE TABLE dbo.myLog
(
    foobar_id INT, 
    oldValue  XML, 
    newValue  XML, 
    [action]  CHAR(6), 
    success   BIT
);
GO

以下INSTEAD OF触发器将拦截INSERT/UPDATE/DELETE命令,尝试复制他们将完成的工作,并记录它是失败还是成功:

CREATE TRIGGER dbo.foobar_inst
ON dbo.foobar
INSTEAD OF INSERT, UPDATE
AS
BEGIN
  SET NOCOUNT ON;
    
  DECLARE @action  CHAR(6), @success BIT;

  SELECT @action  = 'DELETE', @success = 1;
    
  IF EXISTS (SELECT 1 FROM inserted)
  BEGIN
    IF EXISTS (SELECT 1 FROM deleted)
      SET @action = 'UPDATE';
    ELSE
      SET @action = 'INSERT';
  END
    
  BEGIN TRY
    IF @action = 'INSERT'
      INSERT dbo.foobar(id, x) SELECT id, x FROM inserted;
      
    IF @action = 'UPDATE'
      UPDATE f SET x = i.x FROM dbo.foobar AS f
        INNER JOIN inserted AS i ON f.id = i.id;
      
    IF @action = 'DELETE'
        DELETE f FROM dbo.foobar AS f
          INNER JOIN inserted AS i ON f.id = i.id;
  END TRY
  BEGIN CATCH
    ROLLBACK; -- key part here!

    SET @success = 0;
  END CATCH
          
  IF @action = 'INSERT'
    INSERT dbo.myLog SELECT i.id, NULL, 
      (SELECT * FROM inserted WHERE id = i.id FOR XML PATH),
      @action, @success FROM inserted AS i;

  IF @action = 'UPDATE'
    INSERT dbo.myLog SELECT i.id, 
      (SELECT * FROM deleted  WHERE id = i.id FOR XML PATH),
      (SELECT * FROM inserted WHERE id = i.id FOR XML PATH),
      @action, @success FROM inserted AS i;

  IF @action = 'DELETE'
    INSERT dbo.myLog SELECT d.id, 
      (SELECT * FROM deleted  WHERE id = d.id FOR XML PATH),
      NULL, @action, @success FROM deleted AS d;
END
GO

让我们尝试一些非常简单的隐式事务语句:

-- these succeed:

INSERT dbo.foobar SELECT 1, 'x';
GO
INSERT dbo.foobar SELECT 2, 'y';
GO

-- fails with PK violation:

INSERT dbo.foobar SELECT 1, 'z';
GO

-- fails with UQ violation:

UPDATE dbo.foobar SET x = 'y' WHERE id = 1;
GO

检查日志:

SELECT foobar_id, oldValue, newValue, action, success FROM dbo.myLog;

结果:

foobar_id oldValue                      newValue                      action success
--------- ----------------------------- ----------------------------- ------ -------
1         NULL                          <row><id>1</id><x>x</x></row> INSERT 1
2         NULL                          <row><id>2</id><x>y</x></row> INSERT 1
1         NULL                          <row><id>1</id><x>z</x></row> INSERT 0
1         <row><id>1</id><x>x</x></row> <row><id>1</id><x>y</x></row> UPDATE 0

当然,您可能想要日志表中的其他列,例如用户、日期/时间,甚至可能是原始语句。这并不是一个完全全面的审计解决方案,只是一个例子。

正如 Mikael 指出的那样,这依赖于外部批处理是启动隐式事务的单个命令这一事实。如果外部批次是显式的多语句事务,则必须测试该行为。

另请注意,在 UPDATE 影响零行的情况下,这不会捕获“失败”。因此,您需要明确定义“失败”的含义——在某些情况下,您可能需要在外部代码中构建自己的失败处理,而不是在触发器中。

于 2012-06-01T02:59:43.247 回答