2013-11-02 11 views
19

La tabella:cambiamenti record di log in SQL Server in una tabella di controllo

CREATE TABLE GUESTS (
     GUEST_ID int IDENTITY(1,1) PRIMARY KEY, 
     GUEST_NAME VARCHAR(50), 
     GUEST_SURNAME VARCHAR(50), 
     ADRESS VARCHAR(100), 
     CITY VARCHAR(50), 
     CITY_CODE VARCHAR(10), 
     COUNTRY VARCHAR(50), 
     STATUS VARCHAR(20), 
     COMMENT nvarchar(max); 

Per la registrazione:

CREATE TABLE AUDIT_GUESTS (
    ID int IDENTITY(1,1) PRIMARY KEY, 
    GUEST_ID int, 
    OLD_GUEST_NAME VARCHAR(50), 
    NEW_GUEST_NAME VARCHAR(50), 
    OLD_GUEST_SURNAME VARCHAR(50), 
    NEW_GUEST_SURNAME VARCHAR(50), 
    OLD_ADRESS VARCHAR(100), 
    NEW_ADRESS VARCHAR(100), 
    OLD_CITY VARCHAR(50), 
    NEW_CITY VARCHAR(50), 
    OLD_CITY_CODE VARCHAR(10), 
    NEW_CITY_CODE VARCHAR(10), 
    OLD_COUNTRY VARCHAR(50), 
    NEW_COUNTRY VARCHAR(50), 
    OLD_STATUS VARCHAR(20), 
    NEW_STATUS VARCHAR(20), 
    OLD_COMMENT nvarchar(max), 
    NEW_COMMENT nvarchar(max), 
    AUDIT_ACTION varchar(100), 
    AUDIT_TIMESTAMP datetime); 

vorrei creare un trigger sul mio GUESTS tavolo per registrare tutte le modifiche in la mia tabella AUDIT_GUESTS. Come posso farlo in SQL Server 2014 Express?

ho provato:

create TRIGGER trgAfterUpdate ON [dbo].[GUESTS] 
FOR UPDATE 
AS 
    declare @GUEST_ID int; 
    declare @GUEST_NAME varchar(50); 
    declare @GUEST_SURNAME VARCHAR(50); 
    declare @ADRESS VARCHAR(100); 
    declare @CITY VARCHAR(50); 
    declare @CITY_CODE VARCHAR(10); 
    declare @COUNTRY VARCHAR(50); 
    declare @STATUS VARCHAR(20); 
    declare @COMMENT nvarchar(max); 
    declare @AUDIT_ACTION varchar(100); 
    declare @AUDIT_TIMESTAMP datetime; 

    select @GUEST_ID=i.GUEST_ID from inserted i;    
    select @GUEST_NAME=i.GUEST_NAME from inserted i;  
    select @GUEST_SURNAME=i.GUEST_SURNAME from inserted i; 
    select @ADRESS=i.ADRESS from inserted i; 
    select @CITY=i.CITY from inserted i; 
    select @CITY_CODE=i.CITY_CODE from inserted i; 
    select @COUNTRY=i.COUNTRY from inserted i; 
    select @STATUS=i.STATUS from inserted i; 
    select @COMMENT=i.COMMENT from inserted i; 

     if update(GUEST_NAME) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(GUEST_SURNAME) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(ADRESS) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(CITY) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(CITY_CODE) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(COUNTRY) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(STATUS) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     if update(COMMENT) 
     set @audit_action='Updated Record -- After Update Trigger.'; 

     insert into AUDIT_GUESTS 
      (GUEST_ID,GUEST_NAME,GUEST_SURNAME,ADRESS,CITY,CITY_CODE,COUNTRY,STATUS,COMMENT,audit_action,AUDIT_TIMESTAMP) 
    values(@GUEST_ID,@GUEST_NAME,@GUEST_SURNAME,@ADRESS,@CITY,@CITY_CODE,@COUNTRY,@STATUS,@COMMENT,@audit_action,getdate()); 
    GO 

Opere tipo di male, ma vorrei vedere i valori vecchi-nuovi.

In SQLite ho avuto:

CREATE TRIGGER [LOG_UPDATE] 
AFTER UPDATE OF [GUEST_NAME], [GUEST_SURNAME], [ADRESS], [CITY], [CITY_CODE], [COUNTRY], [STATUS], [COMMENT] 
ON [GUESTS] 
BEGIN 
INSERT INTO GUESTS_LOG 
(GUEST_ID, 
    NAME_OLD,NAME_NEW, 
    SURNAME_OLD,SURNAME_NEW, 
    ADRESS_OLD,ADRESS_NEW, 
    CITY_OLD,CITY_NEW, 
    CITY_CODE_OLD,CITY_CODE_NEW, 
    COUNTRY_OLD,COUNTRY_NEW, 
    STATUS_OLD,STATUS_NEW, 
    COMMENT_OLD,COMMENT_NEW,sqlAction,DATE_TIME) 

    VALUES 

(OLD.GUEST_ID, 
    OLD.GUEST_NAME,NEW.GUEST_NAME, 
    OLD.GUEST_SURNAME,NEW.GUEST_SURNAME, 
    OLD.ADRESS,NEW.ADRESS, 
    OLD.CITY,NEW.CITY, 
    OLD.CITY_CODE,NEW.CITY_CODE, 
    OLD.COUNTRY,NEW.COUNTRY, 
    OLD.STATUS,NEW.STATUS, 
    OLD.COMMENT,NEW.COMMENT,'record changed',datetime('now','localtime')); 

END 

e ha funzionato OK. Non so come passare questo al server SQL. Ho appena iniziato ad impararlo.

+5

Il tuo primo e fondamentale difetto nel trigger è che supponiamo che 'Inserted' e' Deleted' contengano solo una singola riga - che è ** NON ** il caso! Se l'istruzione 'UPDATE' ha effetto su 10 righe contemporaneamente, il tuo trigger viene attivato ** una volta ** e ha ** 10 righe ** sia in 'Inserted' che in' Deleted' - devi lavorare con un ** set-based ** approccio e non solo selezionare quello che pensi sia l'unico valore da quelle pseudo tabelle! –

risposta

54

Dai un'occhiata a this article su Simple-talk.com entro il Pop Rivett. Ti guida attraverso la creazione di un trigger generico che registrerà OLDVALUE, NEWVALUE per tutte le colonne aggiornate. Il codice è molto generico ed è possibile applicarlo a qualsiasi tabella che si desidera controllare, anche per qualsiasi operazione CRUD, ad esempio INSERT, UPDATE e DELETE. L'unico requisito è che la tabella da controllare abbia una CHIAVE PRIMARIA (che comunque le tabelle più ben progettate dovrebbero avere).

Ecco il codice pertinente per la tabella OSPITI.

1) Creare la tabella di AUDIT.

IF NOT EXISTS 
     (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[Audit]') 
       AND OBJECTPROPERTY(id, N'IsUserTable') = 1) 
     CREATE TABLE Audit 
       (Type CHAR(1), 
       TableName VARCHAR(128), 
       PK VARCHAR(1000), 
       FieldName VARCHAR(128), 
       OldValue VARCHAR(1000), 
       NewValue VARCHAR(1000), 
       UpdateDate datetime, 
       UserName VARCHAR(128)) 
GO 

2) creare un trigger di aggiornamento nella tabella PERSONE come segue.

CREATE TRIGGER TR_GUESTS_AUDIT ON GUESTS FOR UPDATE 
AS 

DECLARE @bit INT , 
     @field INT , 
     @maxfield INT , 
     @char INT , 
     @fieldname VARCHAR(128) , 
     @TableName VARCHAR(128) , 
     @PKCols VARCHAR(1000) , 
     @sql VARCHAR(2000), 
     @UpdateDate VARCHAR(21) , 
     @UserName VARCHAR(128) , 
     @Type CHAR(1) , 
     @PKSelect VARCHAR(1000) 


--You will need to change @TableName to match the table to be audited. 
-- Here we made GUESTS for your example. 
SELECT @TableName = 'GUESTS' 

-- date and user 
SELECT   @UserName = SYSTEM_USER , 
     @UpdateDate = CONVERT (NVARCHAR(30),GETDATE(),126) 

-- Action 
IF EXISTS (SELECT * FROM inserted) 
     IF EXISTS (SELECT * FROM deleted) 
       SELECT @Type = 'U' 
     ELSE 
       SELECT @Type = 'I' 
ELSE 
     SELECT @Type = 'D' 

-- get list of columns 
SELECT * INTO #ins FROM inserted 
SELECT * INTO #del FROM deleted 

-- Get primary key columns for full outer join 
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on') 
       + ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME 
     FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk , 

       INFORMATION_SCHEMA.KEY_COLUMN_USAGE c 
     WHERE pk.TABLE_NAME = @TableName 
     AND  CONSTRAINT_TYPE = 'PRIMARY KEY' 
     AND  c.TABLE_NAME = pk.TABLE_NAME 
     AND  c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME 

-- Get primary key select for insert 
SELECT @PKSelect = COALESCE(@PKSelect+'+','') 
     + '''<' + COLUMN_NAME 
     + '=''+convert(varchar(100), 
coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>''' 
     FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk , 
       INFORMATION_SCHEMA.KEY_COLUMN_USAGE c 
     WHERE pk.TABLE_NAME = @TableName 
     AND  CONSTRAINT_TYPE = 'PRIMARY KEY' 
     AND  c.TABLE_NAME = pk.TABLE_NAME 
     AND  c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME 

IF @PKCols IS NULL 
BEGIN 
     RAISERROR('no PK on table %s', 16, -1, @TableName) 
     RETURN 
END 

SELECT   @field = 0, 
     @maxfield = MAX(ORDINAL_POSITION) 
     FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName 
WHILE @field < @maxfield 
BEGIN 
     SELECT @field = MIN(ORDINAL_POSITION) 
       FROM INFORMATION_SCHEMA.COLUMNS 
       WHERE TABLE_NAME = @TableName 
       AND ORDINAL_POSITION > @field 
     SELECT @bit = (@field - 1)% 8 + 1 
     SELECT @bit = POWER(2,@bit - 1) 
     SELECT @char = ((@field - 1)/8) + 1 
     IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) & @bit > 0 
             OR @Type IN ('I','D') 
     BEGIN 
       SELECT @fieldname = COLUMN_NAME 
         FROM INFORMATION_SCHEMA.COLUMNS 
         WHERE TABLE_NAME = @TableName 
         AND ORDINAL_POSITION = @field 
       SELECT @sql = ' 
insert Audit ( Type, 
       TableName, 
       PK, 
       FieldName, 
       OldValue, 
       NewValue, 
       UpdateDate, 
       UserName) 
select ''' + @Type + ''',''' 
     + @TableName + ''',' + @PKSelect 
     + ',''' + @fieldname + '''' 
     + ',convert(varchar(1000),d.' + @fieldname + ')' 
     + ',convert(varchar(1000),i.' + @fieldname + ')' 
     + ',''' + @UpdateDate + '''' 
     + ',''' + @UserName + '''' 
     + ' from #ins i full outer join #del d' 
     + @PKCols 
     + ' where i.' + @fieldname + ' <> d.' + @fieldname 
     + ' or (i.' + @fieldname + ' is null and d.' 
           + @fieldname 
           + ' is not null)' 
     + ' or (i.' + @fieldname + ' is not null and d.' 
           + @fieldname 
           + ' is null)' 
       EXEC (@sql) 
     END 
END 

GO 
+0

Ciò richiede varie combinazioni che tendono a diventare troppo lunghe. Un modo piacevole è avere valori vecchi e nuovi. Ecco perché ho chiesto. Come posso registrare vecchi valori nuovi? – user763539

+1

Ok, quindi stai cercando un modo per registrare QUALSIASI campo aggiornato alla tabella dei log. Se 3 campi sono stati aggiornati, si desidera registrare 3 righe separate in audit, 1 per campo modificato con valori OLD e NEW in quella riga di controllo? – Shiva

+0

Sì, esattamente ... Ho appena messo 3 campi per renderlo semplice. Fondamentalmente voglio registrarli tutti. – user763539

-1

Hey E 'molto semplice vedere questo

@OLD_GUEST_NAME = d.GUEST_NAME da cancellato d;

questa variabile consente di memorizzare il vecchio valore eliminato e quindi è possibile inserirlo nel punto desiderato.

per esempio-

Create trigger testupdate on test for update, delete 
    as 
declare @tableid varchar(50); 
declare @testid varchar(50); 
declare @newdata varchar(50); 
declare @olddata varchar(50); 


select @tableid = count(*)+1 from audit_test 
select @testid=d.tableid from inserted d; 
select @olddata = d.data from deleted d; 
select @newdata = i.data from inserted i; 

insert into audit_test (tableid, testid, olddata, newdata) values (@tableid, @testid, @olddata, @newdata) 

go 
1

So che questo è vecchio, ma forse questo sarà aiutare qualcun altro.

Non registrare valori "nuovi". La tua tabella esistente, GUESTS, ha i nuovi valori. Avrai una doppia immissione di dati, inoltre le dimensioni del tuo DB cresceranno troppo velocemente in questo modo.

Ho pulito questo in su e ridotti al minimo per questo esempio, ma qui è la tabelle avresti bisogno di disconnessione modifiche:

CREATE TABLE GUESTS (
     GuestID INT IDENTITY(1,1) PRIMARY KEY, 
     GuestName VARCHAR(50), 
     ModifiedBy INT, 
     ModifiedOn DATETIME 
) 

CREATE TABLE GUESTS_LOG (
     GuestLogID INT IDENTITY(1,1) PRIMARY KEY, 
     GuestID INT, 
     GuestName VARCHAR(50), 
     ModifiedBy INT, 
     ModifiedOn DATETIME 
) 

Quando cambia un valore nella tabella PERSONE (es: nome Ospite) , è sufficiente disconnettere l'intera riga di dati, così com'è, nella tabella Log/Audit utilizzando il Trigger. La tua tabella GUESTS ha dati correnti, la tabella Log/Audit ha i vecchi dati.

Poi utilizzare un'istruzione SELECT per ottenere i dati da entrambe le tabelle:

SELECT 0 AS 'GuestLogID', GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS] WHERE GuestID = 1 
UNION 
SELECT GuestLogID, GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS_LOG] WHERE GuestID = 1 
ORDER BY ModifiedOn ASC 

I Suoi dati uscirà con ciò che il tavolo sembrava, dal più vecchio al più recente, con la prima riga di essere ciò che è stato creato & la l'ultima riga corrisponde ai dati correnti. Puoi vedere esattamente cosa è cambiato, chi lo ha cambiato e quando l'hanno cambiato.

Opzionalmente, avevo una funzione che collegava il RecordSet (in ASP classico) e visualizzava solo i valori modificati nella pagina web. Ha fatto un GRANDI audit trail in modo che gli utenti potessero vedere cosa era cambiato nel tempo.

+0

Utile solo se hai un campo da registrare ... – user763539

+2

@ user763539 Un campo? Si disconnette l'intera riga corrente nella tabella che si sta modificando. Quindi se funzionerà su TUTTI i campi che aggiorni. – Bob

Problemi correlati