logo

[ELMA3] История изменений полей объекта с использованием триггеров в БД

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

Триггер (база данных) – это хранимая процедура особого типа, которую пользователь не вызывает непосредственно, а исполнение которой обусловлено действием по модификации данных: добавлением INSERT, удалением DELETE строки в заданной таблице, или изменением UPDATE данных в определенном столбце заданной таблицы реляционной базы данных.

Использование триггера позволяет дополнительно не нагружать сервер приложения и является одним из лучших способов ведения аудита (запись истории) добавления/удаления/изменения данных в таблице.

Внимание!

Одним из минусов работы с триггерами является то, что при обновлении системы они пропадают, поэтому необходимо в ручном режиме запускать повторное создание триггеров путем запуска заранее сохранённого запроса через менеджер БД.

Рассмотрим простой пример создания триггера на изменение поля ИНН в таблице Контрагент и добавление этой информации в другую. Перед этим необходимо создать таблицу либо в Microsoft SQL Management Studio с помощью запроса CREATE TABLE или через ELMA путем создания нового объекта с необходимыми свойствами.

AFTER INSERT\UPDATE\DELETE – запуск триггера после добавления\обновления\удаления данных в объекте.

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

1
2
3
4
5
6
7
8
9
10
11
12
//MS SQL
CREATE TRIGGER [dbo].[ChangeINN]
   ON  [dbo].[Contractor]  
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    //Добавляем данные в таблицу cо следующими полями (Название контрагента, Название поля, Новое значение поля, Дата изменения)
    insert into  dbo.IstoriyaRabotySObjektami(Naimenovanie,NazvaniePolya,ZnacheniePolya,
    DataVremyaIzmeneniya)
    select  Name,’ИНН’, INN , GETDATE() from INSERTED
END

В данном примере при любом изменении будет добавляться запись в таблицу истории изменений и будет записываться текущее значение ИНН.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//MS SQL
CREATE TRIGGER [dbo].[ContractorUpdate] ON [dbo].[Contractor]
AFTER UPDATE
AS
/**Объявляем переменные**/
DECLARE
 
            @new_Name                   NVARCHAR (max)
        ,   @new_LegalAddress               NVARCHAR (max)
        ,   @new_INN                        NVARCHAR (max)
        ,   @uid                        NVARCHAR (max)
 
        ,   @old_Name                   NVARCHAR (max)
        ,   @old_LegalAddress               NVARCHAR (max)
        ,   @old_INN                        NVARCHAR (max)
 
BEGIN
SET NOCOUNT ON;
/**Присваиваем значение переменным**/
 SELECT @new_Name = Name,
        @new_LegalAddress = (SELECT Name FROM dbo.Address ad WHERE ins.LegalAddress = ad.id),
     @new_INN = INN,
     @uid = "uid"
 FROM   INSERTED  ins /**Измененные значения полей таблицы**/
 
 
SELECT @old_Name = Name,
        @old_LegalAddress = (SELECT Name FROM dbo.Address ad WHERE del.LegalAddress = ad.id),
     @old_INN = INN
 FROM   DELETED del    /**Старые значения полей таблицы**/
 
 
 /**Проверяем изменилось ли значение выбранных полей, если да то записываем в таблицу истории изменений**/
 IF @old_Name<>@new_Name
 BEGIN
    insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (@uid,’Наименование’,@old_Name,@new_Name,GETDATE())
 END
 
 IF @old_LegalAddress<>@new_LegalAddress
 BEGIN
    insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (@uid,’Юридический адрес’,@old_LegalAddress,@new_LegalAddress,GETDATE())
 END
 
 IF @old_INN<>@new_INN
 BEGIN
    insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (@uid,’ИНН’,@old_INN,@new_INN,GETDATE())
 END
 SET NOCOUNT OFF;
END

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

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

В заключение рассмотрим аналогичный вариант в БД Firebird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//Firebird
CREATE TRIGGER tr_Update ON Contractor
AFTER UPDATE
AS
/**Объявляем переменные**/
DECLARE VARIABLE   
            new_Name                    NVARCHAR (max);
            new_LegalAddress                NVARCHAR (max);
            new_INN                     NVARCHAR (max);
            uid                     NVARCHAR (max);
 
            old_Name                    NVARCHAR (max);
            old_LegalAddress                NVARCHAR (max);
               old_INN                      NVARCHAR (max);
 
BEGIN
/**Присваиваем значение переменным**/
 SELECT new_Name into :Name,
        new_LegalAddress into :(SELECT Name FROM dbo.Address ad WHERE ins.LegalAddress = ad.id),
     new_INN into :INN,
     uid into :"uid"
 FROM   INSERTED  ins /**Измененные значения полей таблицы**/
 
 
SELECT old_Name into :Name,
       old_LegalAddress into :(SELECT Name FROM dbo.Address ad WHERE del.LegalAddress = ad.id),
     old_INN into :INN
 FROM   DELETED del    /**Старые значения полей таблицы**/
 
 
 /**Проверяем изменилось ли значение выбранных полей, если да то записываем в таблицу истории изменений**/
 IF (old_Name<>new_Name)
 THEN   insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (uid,’Наименование’,old_Name,new_Name,GETDATE())
 
 IF (old_LegalAddress<>new_LegalAddress)
 THEN insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (uid,’Юридический адрес’,old_LegalAddress,new_LegalAddress,GETDATE())
 
 IF (old_INN<>new_INN)
 THEN   insert into dbo.IstoriyaIzmeneniyaNeskoljkoP (UidKontragenta,NazvaniePolya,StaroeZnacheniePolya,NovoeZnacheniePolya,DataIzmeneniya)
    values (uid,’ИНН’,old_INN,new_INN,GETDATE())
END