SQL Server中的触发器 - 获取为Audit Table完成的事务类型

时间:2012-06-01 02:51:22

标签: sql-server sql-server-2005 triggers

我正在编写触发器,用于将记录插入我的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

1 个答案:

答案 0 :(得分: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影响零行的情况下,这不会捕获“失败”。因此,您需要明确定义“失败”的含义 - 在某些情况下,您可能需要在外部代码中构建自己的失败处理,而不是在触发器中。