Записывать изменения в таблицу базы данных с помощью триггера - PullRequest
9 голосов
/ 23 мая 2009

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

Ответы [ 11 ]

7 голосов
/ 12 мая 2011

Если вы просто хотите сохранить журнал всех транзакций (вставить, обновить и удалить) в некоторых таблицах базы данных, вы можете запустить следующий скрипт:

IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= 'Audit')
  CREATE TABLE LogTable
  (
    LogID [int]IDENTITY(1,1) NOT NULL,
    Type char(1), 
    TableName varchar(128), 
    PrimaryKeyField varchar(1000), 
    PrimaryKeyValue varchar(1000), 
    FieldName varchar(128), 
    OldValue varchar(1000), 
    NewValue varchar(1000), 
    UpdateDate datetime DEFAULT (GetDate()), 
    UserName varchar(128)
  )
GO

DECLARE @sql varchar(8000), @TABLE_NAME sysname
SET NOCOUNT ON

SELECT @TABLE_NAME= MIN(TABLE_NAME)
FROM INFORMATION_SCHEMA.Tables 
WHERE 
--query for table that you want to audit
TABLE_TYPE= 'BASE TABLE' 
AND TABLE_NAME!= 'sysdiagrams'
AND TABLE_NAME!= 'LogTable'
AND TABLE_NAME!= 'one table to not record de log';

WHILE @TABLE_NAME IS NOT NULL
  BEGIN

  SELECT 'PROCESANDO ' + @TABLE_NAME;

  EXEC('IF OBJECT_ID (''' + @TABLE_NAME+ '_ChangeTracking'', ''TR'') IS NOT NULL DROP TRIGGER ' + @TABLE_NAME+ '_ChangeTracking')


  SELECT @sql = 'create trigger ' + @TABLE_NAME+ '_ChangeTracking on ' + @TABLE_NAME+ ' for insert, update, delete
    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) ,
        @PKFieldSelect varchar(1000),
        @PKValueSelect varchar(1000)

        select @TableName = ''' + @TABLE_NAME+ '''

        -- date and user
        select @UserName = system_user ,
        @UpdateDate = convert(varchar(8), getdate(), 112) + '' '' + convert(varchar(12), getdate(), 114)

        -- 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 fields select for insert(comma deparated)           
        select @PKFieldSelect = coalesce(@PKFieldSelect+''+'','''') + '''''''' + 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 values for insert(comma deparated as varchar)           
        select @PKValueSelect = coalesce(@PKValueSelect+''+'','''') + ''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 @sql = ''insert LogTable(Type, TableName, PrimaryKeyField, PrimaryKeyValue, UserName)''
        select @sql = @sql + '' select '''''' + @Type + ''''''''
        select @sql = @sql + '','''''' + @TableName + ''''''''
        select @sql = @sql + '','' + @PKFieldSelect
        select @sql = @sql + '','' + @PKValueSelect
        select @sql = @sql + '','''''' + @UserName + ''''''''

        select @sql = @sql + '' from #ins i full outer join #del d''
        select @sql = @sql + @PKCols        

        exec (@sql)
    ';
  SELECT @sql
  EXEC(@sql)


  SELECT @TABLE_NAME= MIN(TABLE_NAME) FROM INFORMATION_SCHEMA.Tables 
    WHERE TABLE_NAME> @TABLE_NAME
    --query for table that you want to audit
    AND TABLE_TYPE= 'BASE TABLE' 
    AND TABLE_NAME!= 'sysdiagrams'
    AND TABLE_NAME!= 'LogTable'
    AND TABLE_NAME!= 'one table to not record de log';
END
5 голосов
/ 23 мая 2009

Вы должны быть в состоянии сделать это, используя представления управления системой .

Примером будет что-то вроде этого:

SELECT er.session_id,
  er.status,
  er.command,
  DB_NAME(database_id) AS 'DatabaseName',
  user_id,
  st.text
FROM sys.dm_exec_requests AS er
  CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS st
WHERE er.session_id = @@SPID;

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

4 голосов
/ 23 мая 2009

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

2 голосов
/ 03 апреля 2012

Существует шаблон для создания таких триггеров, который называется Log Trigger . Это не зависит от производителя и очень просто. Это описано в здесь .

Изменения записаны в другой таблице history . Нет точного способа получить точное утверждение, но можно определить, была ли это вставка, и обновить или удалить, потому что это создает «связанный» набор записей. Вставка - это запись без предшественника, удаление - это запись без преемника, промежуточные записи - это обновления. Изменения могут быть обнаружены при сравнении записи с ее предшественником.

Очень просто получить снимок одного объекта (или всей таблицы) в данный момент времени.

В качестве бонуса, синтаксис этого шаблона для SQL Server оказывается самым простым по сравнению с Oracle, DB2 и MySQL.

2 голосов
/ 23 мая 2009

В MSSQL есть виртуальные таблицы с именами «Вставлено» и «Удалено», которые содержат вновь вставленные и / или вновь удаленные и / или недавно обновленные записи данных, к которым вы можете обращаться из триггера ... Я использую их, знать, какие данные изменились (вместо того, чтобы сказать, какой оператор изменил данные).

1 голос
/ 08 февраля 2013

Использовать Журнал запуска

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

1 голос
/ 23 мая 2009

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

Как подсказывает @mwigdahl, представления управления системой выглядят как хороший способ для захвата текущего запущенного пакета. Является ли это особенно полезным для входа в триггер, это другое дело.

Недостатком использования триггеров является то, что вы можете определить источник обновления только из соединения с базой данных. Многие приложения не имеют никакой пользовательской информации, связанной с соединением, чтобы упростить пул соединений, поэтому вы не знаете, какой пользователь выполняет действие. т. е. имя входа, используемое соединением, является общим именем пользователя приложения, а не лицом, использующим приложение. Обычный способ обойти это - использовать хранимые процедуры в качестве интерфейса для всех взаимодействий с базой данных, а затем убедиться, что UserId передается со всеми вызовами процедур. Затем вы можете выполнить регистрацию через хранимую процедуру вместо триггера. Очевидно, что это полезно только в том случае, если вы знаете, что люди не будут обновлять таблицы напрямую, не используя процедуры, или вам не нужно регистрировать эту ситуацию.

Возможность получить выполняемый в данный момент пакет может обеспечить еще лучший механизм: если вы убедитесь, что все ваши sql-пакеты содержат UserId, вы можете извлечь его из sql в вашем триггере. Это позволило бы вам вести всю регистрацию с помощью триггеров, что означает, что вы захватываете все, но также позволяет связывать изменения с конкретным пользователем.

Если вы идете по маршруту триггера, стоит проверить, что триггеры ситуаций не срабатывают (может быть, загружаются объемные данные? Или если у людей есть разрешение на отключение триггеров).

Также учтите, что @idstam указал, что код триггера будет находиться в вашей транзакции, поэтому обычно регистрируется и откатывается вместе с ним.

Еще одна вещь, которую следует учитывать при написании триггеров, это поведение @@ IDENTITY : если у вас есть процедуры, использующие @@ IDENTITY, вы можете случайно изменить их поведение.

1 голос
/ 23 мая 2009

Триггеры плохие, я бы держался подальше от триггеров.

Если вы пытаетесь устранить неполадки, присоедините Sql Profiler к базе данных с конкретными условиями. Это будет регистрировать каждый запрос, выполненный для вашей проверки.

Другой вариант - переключиться на вызывающую программу для записи запросов. Это очень распространенная практика.

1 голос
/ 23 мая 2009

вам действительно нужно регистрировать оператор, который выполнялся, большинство людей регистрируют измененные данные (таблицы INSERTED и DELETED в триггере).

0 голосов
/ 17 апреля 2013

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

Несколько других вещей, которые следует учитывать:

Планирование хранилища - если у вас много обновлений, это означает, что у вас будет масса данных аудита. Я хотел бы хранить эти данные в отдельных базах данных. Особенно, если вы планируете проверять более одной базы данных.

Управление объемом данных - со временем вам, вероятно, не понадобится вести очень старые записи. План легкого удаления старых данных

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

Принимая все это во внимание, вероятно, наиболее эффективно использовать какое-то уже разработанное решение вместо создания с нуля.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...