복붙노트

[SQL] 감사 테이블에 SQL 서버의 로그 레코드 변경

SQL

감사 테이블에 SQL 서버의 로그 레코드 변경

탁자 :

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

로깅의 경우 :

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

내 AUDIT_GUESTS 테이블의 모든 변경 사항을 기록 내 손님 테이블에 트리거를 작성하고 싶습니다. 어떻게 SQL 서버 2014 Express에서 그렇게 할 수 있습니까?

나는 시도했다 :

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

종류 확인의 작동하지만 오래된 새로운 가치를보고 싶습니다.

SQLite는 나는했다 :

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

그것은 확인을했습니다. 그냥 해달라고 SQL 서버에이를 전달하는 방법을 알고있다. 그냥 학습 시작.

해결법

  1. ==============================

    1.팝 Rivett에 의해 Simple-talk.com에이 기사를 살펴 보자. 그것은 모든 업데이트 열에 OLDVALUE, NEWVALUE를 기록하는 일반적인 트리거를 만드는 과정을 안내합니다. 코드는 매우 일반적이며, 당신은 어떤 CRUD 작업 즉 INSERT, UPDATE에 대한 또한, 당신이 감사 싶어 모든 테이블에 적용하고 삭제할 수 있습니다. 유일한 요구 사항은 테이블 (대부분의 잘 설계된 테이블이 어쨌든해야한다) 기본 키가 있어야 감사 할 것입니다.

    팝 Rivett에 의해 Simple-talk.com에이 기사를 살펴 보자. 그것은 모든 업데이트 열에 OLDVALUE, NEWVALUE를 기록하는 일반적인 트리거를 만드는 과정을 안내합니다. 코드는 매우 일반적이며, 당신은 어떤 CRUD 작업 즉 INSERT, UPDATE에 대한 또한, 당신이 감사 싶어 모든 테이블에 적용하고 삭제할 수 있습니다. 유일한 요구 사항은 테이블 (대부분의 잘 설계된 테이블이 어쨌든해야한다) 기본 키가 있어야 감사 할 것입니다.

    여기 손님 테이블에 대한 관련 코드입니다.

    1) 감사 테이블을 만듭니다.

    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) 참석자 테이블에 UPDATE 트리거를 작성.

    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
    
  2. ==============================

    2.나는 이것이 오래 알고,하지만 어쩌면이 다른 사람을 도움이 될 것입니다.

    나는 이것이 오래 알고,하지만 어쩌면이 다른 사람을 도움이 될 것입니다.

    "새로운"값을 기록하지 마십시오. 기존 테이블, 손님, 새로운 값이 있습니다. 당신은 데이터의 두 항목이 있습니다, 플러스 DB 크기가 너무 빠른 방법 그런 식으로 성장할 것입니다.

    나는이 최대를 청소하고이 예를 최소화하지만, 여기에 변경 사항을 로그 오프에 필요한 것 테이블입니다 :

    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
    )
    

    값이 손님 테이블 (예 : 고객 이름)으로 변경하면 트리거를 사용하여 로그 / 감사 테이블에,있는 그대로, 단순히 데이터의 전체 행을 로그 오프합니다. 당신의 손님 테이블은 로그 / 감사 테이블은 이전 데이터를 가지고, 현재의 데이터가 있습니다.

    그런 다음 두 테이블에서 데이터를 얻을 수있는 선택 문을 사용 :

    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
    

    귀하의 데이터는 테이블의 첫 번째 행 생성 및 마지막 행은 현재의 데이터되고 있었는지 인 상태, 오래된에서 최신에, 어떻게 생겼는지 함께 올 것이다. 그들은 그것을 변경 할 때 당신은 그것을 변경하는 사람들 정확히 변경을 참조 할 수 있습니다.

    선택적으로, 나는 (클래식 ASP 단위) 레코드 집합을 통해 루프 기능을 가지고 사용하고, 단지 웹 페이지에서 변경 한 것을 값으로 표시됩니다. 그것은 사용자가 시간이 지남에 변경이 있었는지 볼 수 있도록 GREAT 감사 추적을 위해 만든.

  3. ==============================

    3.이 두 가지 버그 수정 코드입니다. 첫 번째 버그 수정은이 질문에 대한 허용 대답에 코멘트에 Royi Namir 언급했다. 버그는 트리거 코드에서 버그에서에 StackOverflow에 설명되어 있습니다. 두 번째는 자신의 이름에 대한 배수 단어 Fandango68 및 수정 열 @에 의해 발견되었다.

    이 두 가지 버그 수정 코드입니다. 첫 번째 버그 수정은이 질문에 대한 허용 대답에 코멘트에 Royi Namir 언급했다. 버그는 트리거 코드에서 버그에서에 StackOverflow에 설명되어 있습니다. 두 번째는 자신의 이름에 대한 배수 단어 Fandango68 및 수정 열 @에 의해 발견되었다.

    ALTER TRIGGER [dbo].[TR_person_AUDIT]
    ON [dbo].[person]
    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 = 'PERSON'
    
               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(COLUMN_NAME) 
                      @maxfield = -- FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
    
    
                      MAX(
                          COLUMNPROPERTY(
                              OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
                              COLUMN_NAME,
                              'ColumnID'
                          )
                      )
               FROM   INFORMATION_SCHEMA.COLUMNS
               WHERE  TABLE_NAME = @TableName
    
    
    
    
    
    
               WHILE @field < @maxfield
               BEGIN
                   SELECT @field = MIN(
                              COLUMNPROPERTY(
                                  OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
                                  COLUMN_NAME,
                                  'ColumnID'
                              )
                          )
                   FROM   INFORMATION_SCHEMA.COLUMNS
                   WHERE  TABLE_NAME = @TableName
                          AND COLUMNPROPERTY(
                                  OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
                                  COLUMN_NAME,
                                  'ColumnID'
                              ) > @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 COLUMNPROPERTY(
                                      OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
                                      COLUMN_NAME,
                                      'ColumnID'
                                  ) = @field
    
    
    
                       SELECT @sql = 
                              '
               insert into 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
    
  4. ==============================

    4.이봐, 그것은 매우 간단이 참조

    이봐, 그것은 매우 간단이 참조

    @OLD_GUEST_NAME = d.GUEST_NAME 삭제 D에서;

    이 변수는 이전 삭제 된 값을 저장하고 당신이 원하는 곳에 당신은 그것을 삽입 할 수 있습니다.

    예 -에 대한

    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
    
  5. from https://stackoverflow.com/questions/19737723/log-record-changes-in-sql-server-in-an-audit-table by cc-by-sa and MIT license