Как создать общую хранимую процедуру SQL Server для выполнения вставок в таблицу аудита на основе вставленных и удаленных в триггере - PullRequest
3 голосов
/ 16 января 2012

Я внедрил структуру журнала аудита на основе информации, предоставленной в первом ответе на следующее сообщение:

Таблица истории SQL Server - заполнить с помощью SP или Trigger?

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

Мои триггеры аудита вставки и удаления довольно просты.Однако триггеры обновления намного сложнее, потому что триггер должен проверить, чтобы определить, находится ли каждый столбец под контролем аудита, а затем выполнить вставку, основываясь на том, равны ли значения столбцов в столбцах «Вставлено» и «Удалено» или нет, посколькуЯ не хочу писать ненужные записи аудита.В конечном счете, я хочу знать, есть ли способ написать хранимую процедуру, которая уменьшит объем кода в моем триггере, позволяя мне динамически выполнять оператор вставки ниже.По сути, я представляю триггер, запускающий sproc с каждым именем столбца, которое находится под контролем аудита, и затем хранимая процедура будет использовать имя столбца для выполнения фрагмента кода ниже.В настоящее время у меня есть код ниже для каждого столбца, который находится под контролем аудита, что, к сожалению, приводит к большому количеству избыточного кода.

Пересмотренный триггер после предложенных изменений

CREATE TRIGGER [dbo].[Audit_Customers_Update] ON [dbo].[Customers]
FOR UPDATE AS

select FirstName,LastName into #deleted from deleted;

declare /*const*/ @TABLE_NAME sysname = '[table name]';

declare f cursor
local
forward_only
read_only
for
  select c.name, quotename(c.name, '[')
  from
    sys.columns c
    inner join sys.types t on c.system_type_id = t.system_type_id
  where
    c.object_id = object_id(@TABLE_NAME)
    and c.is_computed = 0
    and c.is_identity = 0
    and t.name not in ('text', 'image', 'timestamp', 'xml')
    and (substring(COLUMNS_UPDATED(), ((c.column_id - 1) / 8) + 1, 1) & power(2, (c.column_id - 1) % 8)) > 0
  ;

declare @field_name sysname, @field_name_sanitised sysname;
create table #results (row_id int not null,
                       field_name sysname not null,
                       oldval nvarchar(150) null,
                       newval nvarchar(150) null);

-- For each changed field, insert what exactly changed into #results

open f;

fetch next from f into @field_name, @field_name_sanitised;
while @@fetch_status = 0
begin
  declare @query nvarchar(4000);

  set @query =  N'insert into #results(row_id, field_name, oldval, newval)
                  select d.row_id, @field_name, d.' + @field_name_sanitised + N', i.' + @field_name_sanitised + N'
                  from
                    #deleted d inner join ' + @TABLE_NAME + N' i on d.row_id = i.row_id
                  where
                    (d.' + @field_name_sanitised + N' <> i.' + @field_name_sanitised + N')
                    or
                    (case when d.' + @field_name_sanitised + N' is null then 1 else 0 end <> case when i.' + @field_name_sanitised + N' is null then 1 else 0 end);'
                ;    

  exec sp_executesql
    @stmt = @query,
    @params = N'@field_name sysname',
    @field_name = @field_name
  ;

  fetch next from f into @field_name, @field_name_sanitised;
end;

close f;
deallocate f;

-- Do something meaningful to #results here

Как получить доступ к #results?Нужно ли использовать курсор?

Ответы [ 2 ]

6 голосов
/ 16 января 2012

Мы решили эту проблему следующим образом.

select <list of tracked columns here> into #deleted from deleted;

declare /*const*/ @TABLE_NAME sysname = '[table name]';

declare f cursor
local
forward_only
read_only
for
  select c.name, quotename(c.name, '[')
  from
    sys.columns c
    inner join sys.types t on c.system_type_id = t.system_type_id
  where
    c.object_id = object_id(@TABLE_NAME)
    and c.is_computed = 0
    and c.is_identity = 0
    and t.name not in ('text', 'image', 'timestamp', 'xml')
    and (substring(COLUMNS_UPDATED(), ((c.column_id - 1) / 8) + 1, 1) & power(2, (c.column_id - 1) % 8)) > 0
  ;

declare @field_name sysname, @field_name_sanitised sysname;
create table #results (row_id int not null, field_name sysname not null, oldval nvarchar(150) null, newval nvarchar(150) null);

-- For each changed field, insert what exactly changed into #results

open f;

fetch next from f into @field_name, @field_name_sanitised;
while @@fetch_status = 0
begin
  declare @query nvarchar(4000);

  set @query =  N'insert into #results(row_id, field_name, oldval, newval)
                  select d.row_id, @field_name, d.' + @field_name_sanitised + N', i.' + @field_name_sanitised + N'
                  from
                    #deleted d inner join ' + @TABLE_NAME + N' i on d.row_id = i.row_id
                  where
                    (d.' + @field_name_sanitised + N' <> i.' + @field_name_sanitised + N')
                    or
                    (case when d.' + @field_name_sanitised + N' is null then 1 else 0 end <> case when i.' + @field_name_sanitised + N' is null then 1 else 0 end);'
                ;    

  exec sp_executesql
    @stmt = @query,
    @params = N'@field_name sysname',
    @field_name = @field_name
  ;

  fetch next from f into @field_name, @field_name_sanitised;
end;

close f;
deallocate f;

-- Do something meaningful to #results here

Связанные чтения:

1 голос
/ 16 апреля 2014

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

На основеданное решение здесь

XML извлекается с помощью FOR XML из триггера, который обновил таблицу ... "OldValues" происходят из таблицы DELETED, а "NewValues" из таблицы INSERTED... итоговый xml выглядит так ...

            DECLARE @x XML= '<FieldData>
              <UpdatedColumns>
                <trType>OldValues</trType>
                <ID>5</ID>
                <def_label>TEST_TIE</def_label>
                <def_code />
              </UpdatedColumns>
              <UpdatedColumns>
                <trType>OldValues</trType>
                <ID>4</ID>
                <def_label>RP_TIE</def_label>
                <def_code />
              </UpdatedColumns>
              <UpdatedColumns>
                <trType>OldValues</trType>
                <ID>3</ID>
                <def_label>ERR_TIE</def_label>
                <def_code />
              </UpdatedColumns><UpdatedColumns>
                <trType>NewValues</trType>
                <ID>5</ID>
                <def_label>TEST_TIE</def_label>
                <def_code>A</def_code>
              </UpdatedColumns>
              <UpdatedColumns>
                <trType>NewValues</trType>
                <ID>4</ID>
                <def_label>RP_TIE</def_label>
                <def_code>A</def_code>
              </UpdatedColumns>
              <UpdatedColumns>
                <trType>NewValues</trType>
                <ID>3</ID>
                <def_label>ERR_TIE</def_label>
                <def_code>A</def_code>
              </UpdatedColumns>
            </FieldData>'

            declare @timestamp datetime2= SYSDATETIME()

            select 
                     ID = identity(int,1,1), 
                     T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
                     T.N.value('../ID[1]','nvarchar(100)') AS table_ID,
                     T.N.value('.', 'nvarchar(100)') as OldValue
            INTO #old
            from @x.nodes('//UpdatedColumns/*') as T(N)
            WHERE T.N.value('../trType[1]', 'nvarchar(100)') ='OldValues'


            select 
                     ID = identity(int,1,1),
                     T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
                     T.N.value('../ID[1]','nvarchar(100)') AS Table_ID,
                     T.N.value('.', 'nvarchar(100)') as NewValue
            into #new
            from @x.nodes('//UpdatedColumns/*') as T(N)
            WHERE T.N.value('../trType[1]', 'nvarchar(100)') ='NewValues'



            SELECT n.table_ID, n.NodeName, o.OldValue, n.NewValue,@timestamp as transation_time FROM #new n
            left outer JOIN #old o ON n.NodeName = o.NodeName AND n.ID = o.ID 
            WHERE isnull(o.[OldValue],'') <> isnull(n.[newValue],'') AND n.NodeName <> 'trType'



            DROP TABLE #new,#old 
            GO
...