Убедитесь, что триггер SQL Server фиксирует все изменения записей - PullRequest
0 голосов
/ 28 июня 2019

Мы пытаемся отследить изменения записей в таблице SQL Server. Пользователи жалуются, что они изменяют запись утром, а когда они возвращаются днем, значение изменяется обратно.

Пример: значение столбца было нулевым. Пользователь изменил значение, например 6. Когда он вернется к записи, значение может снова быть нулевым или нулевым.

Мы включили аудит в SQL Server, но я не вижу первого редактирования (утра) или возврата обратно к нулю или нулю. Да, я думаю, что ошибка пользователя, но прежде чем обвинять пользователя, я должен устранить все другие возможности. Я вытащил из сети код события триггера и изменил его для своих целей. Я положил его на конкретную таблицу под триггерами. Это тянет записи, как я ожидал. Он отслеживает, какие столбцы были изменены, старые и новые значения. Я признаю, что я не знаком со всем, что делает этот код, поэтому я хочу спросить, есть ли в этом коде потенциальные дыры.

Пример: не будет ли записано событие, если кто-то из-за NULL изменил столбец с 1 на NULL? ModifiedOn и ModifiedBy должны быть автоматически заполнены, когда программа сохраняет запись. Триггер захватит ModifiedOn, но не всегда ModifiedBy. Был задан вопрос, если ModifiedBy совпадает старое значение и новое значение (один и тот же человек внес оба изменения), не будет ли запись в журнале ModifiedBy, поскольку значения совпадают.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER TRIGGER [dbo].[TR_AppointmentBase_LOG] 
ON [dbo].[AppointmentBase] 
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),
                   @PKSelect2      VARCHAR(1000)


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

           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 + ']))+''>''',
                @PKSelect2 = COALESCE(@PKSelect2 + '+', '') 
                  + '+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 AppointmentBase_Log2 ( Type, 
           TableName, 
           PK, 
           FieldName, 
           OldValue, 
           NewValue, 
           UpdateDate, 
           UserName)
           select ''' + @Type + ''',''' 
                          + @TableName + ''',' + @PKSelect2
                          + ',''' + @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

Пользователи и менеджеры думают, что я не нахожу все изменения в таблице с моим кодом. В основном потому, что я не могу доказать, что они изменили запись в первую очередь, не говоря уже о втором изменении. Исходя из моего ограниченного знания описанного выше процесса, я не достаточно уверен, чтобы сказать, что они ошибаются.

Существует ли транзакция, которая может обновить запись в таблице, которую этот код может пропустить? Скажем, запланированное задание обслуживания, которое может не вызывать типы «U», «D» или «I». Или, может быть, пакетная инъекция из внешнего источника?

Или, как вы думаете, я хорошо справляюсь с вышесказанным и должен рассматривать пользовательский или графический интерфейс как не завершивший транзакцию.

...