Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Déclencheur dans SQL Server - Obtenir le type de transaction effectuée pour la table d'audit

Une fois que vous avez fixé votre déclencheur pour couvrir les trois opérations,

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

Une autre alternative est trois déclencheurs distincts, un pour chaque action.

Méfiez-vous de MERGE si vous l'utilisez... Ou préparez-vous à cela lorsque vous passerez à SQL Server 2008 ou au-delà.

MODIFIER

Je pense que ce que vous recherchez est peut-être un INSTEAD OF déclencheur à la place (quelle ironie). Voici un exemple. Considérons un tableau très simple avec une colonne PK et une colonne unique :

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

Et un simple tableau de journal pour capturer l'activité :

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

Le INSTEAD OF suivant le déclencheur interceptera INSERT/UPDATE/DELETE commandes, essayez de reproduire le travail qu'ils auraient fait et enregistrez si c'était un échec ou un succès :

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

Essayons quelques instructions de transaction implicites très simples :

-- 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

Vérifiez le journal :

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

Résultats :

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

Bien sûr, vous voulez probablement d'autres colonnes dans la table de journal, telles que l'utilisateur, la date/l'heure, peut-être même la déclaration d'origine. Ce n'était pas censé être une solution d'audit complète, juste un exemple.

Comme le souligne Mikael, cela repose sur le fait que le lot externe est une seule commande qui démarre une transaction implicite. Le comportement devra être testé si le lot externe est une transaction explicite à plusieurs instructions.

Notez également que cela ne capture pas "l'échec" dans le cas où, par exemple, un UPDATE affecte zéro ligne. Vous devez donc définir explicitement ce que signifie "échec" - dans certains cas, vous devrez peut-être créer votre propre gestion des échecs dans le code externe, pas dans un déclencheur.