Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Trigger in SQL Server:ottieni il tipo di transazione eseguita per la tabella di controllo

Dopo aver corretto il trigger per coprire tutte e tre le operazioni,

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

Un'altra alternativa sono tre trigger separati, uno per ogni azione.

Fai attenzione a MERGE se lo stai utilizzando... Oppure preparati quando passi a SQL Server 2008 o versioni successive.

MODIFICA

Penso che quello che potresti cercare sia un INSTEAD OF innescare invece (che ironia). Ecco un esempio. Consideriamo una tabella molto semplice con una colonna PK e una colonna univoca:

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

E una semplice tabella di registro per rilevare l'attività:

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

Il seguente INSTEAD OF il trigger intercetterà INSERT/UPDATE/DELETE comandi, tentare di replicare il lavoro che avrebbero svolto e registrare se si è verificato un errore o un successo:

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

Proviamo alcune semplici istruzioni di transazione implicita:

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

Controlla il registro:

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

Risultati:

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

Ovviamente probabilmente vorrai altre colonne sulla tabella di registro, come utente, data/ora, forse anche l'istruzione originale. Questa non doveva essere una soluzione di auditing completa, solo un esempio.

Come sottolinea Mikael, questo si basa sul fatto che il batch esterno è un singolo comando che avvia una transazione implicita. Il comportamento dovrà essere testato se il batch esterno è una transazione esplicita con più istruzioni.

Si noti inoltre che questo non acquisisce "errore" nel caso in cui, ad esempio, un UPDATE influisca su zero righe. Quindi è necessario definire in modo esplicito cosa significa "fallimento" - in alcuni casi potrebbe essere necessario creare la propria gestione degli errori nel codice esterno, non in un trigger.