Личная страничка 

Facebook Twitter Gplus RSS
Home Творчество Статьи Логирование в 1С средствами MS SQL
formats

Логирование в 1С средствами MS SQL

По материалам статьи Владимира Сердюка «Реализация системы логирования для 1С(SQL)»

В данной статье описывается вариант минимальной реализации, а конкретнее только «отлавливание» самого факта изменения реквизитов справочников и шапок документов. Спросите зачем?

А все очень просто. Эта статья может вполне восприниматься как продолжение Получение данных из внешней базы 1С. Т.е. если использовать механизмы описанные в упомянутой статье не для разового переноса, а для постоянного обмена данными между базами, то возникает несколько проблем: Нам постоянно необходимо перетягивать ВСЕ содержимое ВСЕХ справочников. Если документ не входит в указанный нами диапазон загрузки, но был в этот период изменен. Тут нас и спасет логирование. Ну и, естественно, непосредственное отслеживание вносимых пользователями изменений.

Для начала создадим две таблицы, которые будут нам необходимы для работы. Одна из них нужна нам будет только для того, чтобы <отлавливать designtimesp=20970> пользователя, который вносит изменения.

CREATE Table dbo.CurrentUsers 
(SPID int NOT NULL CONSTRAINT PK_SPID PRIMARY KEY CLUSTERED,
User1S char (20), UserNT char(20), UserSQL char(20), ClientSQL char(20))

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

CREATE Table dbo.ЖурналИзменений 
(Тип Char(1), Время DateTime, Пользователь Char(60), ИД Char(9), Таблица Char(80), Поля Char(500))

Вторым шагом будет запись идентификатора процесса, при входе пользователя в 1С и удаление этого идентификатора при выходе.

Я делаю это следующим образом:

//_____________________________________________________________________________
Процедура ПриНачалеРаботыСистемы()
    Загрузчик = CreateObject("Toy.Loader");
    Хэндл = Загрузчик.LoadLibrary(КаталогИБ() + "ExtCompsToysql21.dll");
    глТой = СоздатьОбъект("ToyQuery");

    // запись SPID в служебную таблицу
    WshShell = CreateObject("WScript.Shell");
    глТой.Соединиться(0);
    глТой.Выполнить("INSERT INTO dbo.CurrentUsers (SPID, User1S, UserNT, UserSQL, ClientSQL)
        | VALUES (@@SPID,'"+ ИмяПользователя() + "','"+
    WshShell.ExpandEnvironmentStrings("%username%") + "',SYSTEM_USER,'"+
    WshShell.ExpandEnvironmentStrings("%clientname%") + "')");
КонецПроцедуры //ПриНачалеРаботыСистемы

//_____________________________________________________________________________
Процедура ПриЗавершенииРаботыСистемы()
    глТой.Соединиться(0);
    глТой.Выполнить("DELETE FROM dbo.CurrentUsers WHERE SPID = @@SPID");

    Загрузчик.FreeLibrary(Хэндл);
КонецПроцедуры //ПриЗавершенииРаботыСистемы

Ну вот вроде бы подготовительные работы и закончены. Теперь приступим к основному. Т.е. к созданию механизма, который и будет осуществлять запись лога в созданную таблицу. Это, конечно, можно сделать и поправив все модули объектов 1С, добавив в процедуру ПриЗаписи() некоторое количество кода. Ах, да! И еще не забыть отловить во всех других возможных местах конфигурации и внешних обработках, где возможны изменения объектов с последующей записью. Но, как говорил некогда любимый всеми детишками некогда большой страны дедушка Ленин, <мы пойдем другим путем designtimesp=21062>! Мы <нарисуем designtimesp=21063> триггеры на необходимые таблицы.

Какие же таблицы мы выберем? Ну, мне кажется естественным, что прежде всего это будут таблицы справочников (SC*)

Для инд = 1 По Метаданные.Справочник() Цикл
    тСпр = Метаданные.Справочник(инд);
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_D_SC"+ МД.ИДОбъекта(тСпр) + "]");
    Исключение
    КонецПопытки;
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_I_SC"+ МД.ИДОбъекта(тСпр) + "]");
    Исключение
    КонецПопытки;
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_U_SC"+ МД.ИДОбъекта(тСпр) + "]");
    Исключение
    КонецПопытки;
    // удаление
    стр = "CREATE TRIGGER [TR_D_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR DELETE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля    char(500)
    |        DECLARE @ИД        char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SET @Поля = ''
    |        SELECT @ИД = ID FROM DELETED
    |
    |        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |        VALUES('D', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |
    |        SET NOCOUNT OFF";
    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

    // вставка
    стр = "CREATE TRIGGER [TR_I_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR INSERT
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SET @Поля = ''
    |        SELECT @ИД = ID FROM INSERTED
    |
    |        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |        VALUES('I', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |
    |        SET NOCOUNT OFF";
    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;

    // изменение
    стр = "CREATE TRIGGER [TR_U_SC"+ МД.ИДОбъекта(тСпр) + "] ON [dbo].[SC"+ МД.ИДОбъекта(тСпр) + "]
    |        FOR UPDATE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SELECT @Поля = ''
    |";
    Если тСпр.ДлинаКода <> 0 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.CODE <> d.CODE  THEN 'Код,'
        |        ELSE ''
        |        END
        |";
    КонецЕсли;
    Если тСпр.ДлинаНаименования <> 0 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.DESCR <> d.DESCR  THEN 'Наименование,'
        |        ELSE ''
        |        END
        |";
    КонецЕсли;
    Если СокрЛП(Метаданные.Справочник(инд).Владелец) <> "Метаданные"Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.PARENTEXT <> d.PARENTEXT  THEN 'Владелец,'
        |        ELSE ''
        |        END
        |";
    КонецЕсли;
    Если Метаданные.Справочник(инд).КоличествоУровней <> 1 Тогда
        стр = стр + "
        |        +CASE
        |        WHEN i.PARENTID <> d.PARENTID  THEN 'Родитель,'
        |        ELSE ''
        |        END
        |";
    КонецЕсли;
    стр = стр + "
    |        +CASE
    |        WHEN i.ISMARK <> d.ISMARK  THEN 'ПометкаУдаления,'
    |        ELSE ''
    |        END
    |";
    Для чПоле = 1 По тСпр.Реквизит() Цикл
        тПоле = тСпр.Реквизит(чПоле);
        Если тПоле.Периодический = 1 Тогда
            Продолжить;
        КонецЕсли;
        стр = стр + "
        |        +CASE
        |        WHEN i.SP" + МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";
    КонецЦикла;
    стр = стр + "
    |        FROM INSERTED i, DELETED d
    |        WHERE i.Row_ID = d.Row_ID
    |
    |        SELECT @ИД = ID from DELETED
    |
    |        IF @Поля <> ''
    |        BEGIN
    |            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Спр"+ тСпр.Идентификатор + "', @Поля)
    |        END
    |
    |        SET NOCOUNT OFF";
    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;
КонецЦикла;

Что за таинственные объекты СКЛ и МД, я надеюсь, вы помните из предыдущей статьи.  

Теперь возьмемся за документы. Было бы просто великолепно, если бы все необходимые нам изменения находились только в Журнале (_1SJOURN) (кстати, те изменения, которые влияют на необходимость выгрузки/загрузки именно там и находятся, в основном), но придется еще и окучивать таблицы шапок документов (DH*)

// журнал документов (создание нового  удаление документа)
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_D_JOURN]");
Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_D_JOURN] ON [dbo].[_1SJOURN]
|        FOR DELETE
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля    char(500)
|        DECLARE @ИД        char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SET @Поля = ''
|        SELECT @ИД = IDDOC FROM DELETED
|
|        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|        VALUES('D', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|
|        SET NOCOUNT OFF";
Попытка
    СКЛ.Выполнить(стр);
Исключение
КонецПопытки;

// вставка
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_I_JOURN]");
Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_I_JOURN] ON [dbo].[_1SJOURN]
|        FOR INSERT
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля char(500)
|        DECLARE @Ид char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SET @Поля = ''
|        SELECT @ИД = IDDOC FROM INSERTED
|
|        INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|        VALUES('I', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|
|        SET NOCOUNT OFF";
Попытка
    СКЛ.Выполнить(стр);
Исключение
КонецПопытки;

// update общих реквизитов документов
Попытка
    СКЛ.Выполнить("DROP TRIGGER [TR_U_JOURN]");
Исключение
КонецПопытки;
стр = "CREATE TRIGGER [TR_U_JOURN] ON [dbo].[_1SJOURN]
|        FOR UPDATE
|        AS
|        SET NOCOUNT ON
|        DECLARE @CurrentUser char(60)
|        DECLARE @Поля char(500)
|        DECLARE @Ид char(29)
|
|        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
|        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
|        SELECT @Поля = ''
|
|        +CASE
|        WHEN i.DATE_TIME_IDDOC <> d.DATE_TIME_IDDOC  THEN 'ДатаДок,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.DOCNO <> d.DOCNO  THEN 'Номер,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.CLOSED <> d.CLOSED  THEN 'Проведен,'
|        ELSE ''
|        END
|
|        +CASE
|        WHEN i.ISMARK <> d.ISMARK  THEN 'ПометкаУдаления,'
|        ELSE ''
|        END
|";
Для инд = 1 По Метаданные.ОбщийРеквизитДокумента() Цикл
    тПоле = Метаданные.ОбщийРеквизитДокумента(инд);
    Если тПоле.Сортировка = 0 Тогда
        Продолжить;
    КонецЕсли;
    стр = стр + "
    |        +CASE
    |        WHEN i.SP" + МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
    |        ELSE ''
    |        END
    |";
КонецЦикла;
стр = стр + "
|        FROM INSERTED i, DELETED d
|        WHERE i.Row_ID = d.Row_ID
|
|        SELECT @ИД = IDDOC from DELETED
|
|        IF @Поля <> ''
|        BEGIN
|            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
|            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Журнал', @Поля)
|        END
|
|        SET NOCOUNT OFF";
Попытка
    СКЛ.Выполнить(стр);
Исключение
КонецПопытки;

// документы
Для инд = 1 По Метаданные.Документ() Цикл
    тДок = Метаданные.Документ(инд);
    // удаление  вставка отслеживаются пожурналу
    // изменение
    Попытка
        СКЛ.Выполнить("DROP TRIGGER [TR_U_DH"+ МД.ИДОбъекта(тДок) + "]");
    Исключение
    КонецПопытки;
    стр = "CREATE TRIGGER [TR_U_DH"+ МД.ИДОбъекта(тДок) + "] ON [dbo].[DH"+ МД.ИДОбъекта(тДок) + "]
    |        FOR UPDATE
    |        AS
    |        SET NOCOUNT ON
    |        DECLARE @CurrentUser char(60)
    |        DECLARE @Поля char(500)
    |        DECLARE @Ид char(29)
    |
    |        SELECT @CurrentUser = ltrim(rtrim(User1S)) + ';' + ltrim(rtrim(UserNT)) + ';' + ltrim(rtrim(UserSQL)) + ';'
    |        + ltrim(rtrim(ClientSQL)) FROM CurrentUsers WHERE SPID = @@SPID
    |        SELECT @Поля = ''
    |";
    Для чПоле = 1 По Метаданные.ОбщийРеквизитДокумента() Цикл
        тПоле = Метаданные.ОбщийРеквизитДокумента(чПоле);
        Если тПоле.Сортировка = 1 Тогда
            Продолжить;
        КонецЕсли;
        стр = стр + "
        |        +CASE
        |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";
    КонецЦикла;
    Для чПоле = 1 По тДок.РеквизитШапки() Цикл
        тПоле = тДок.РеквизитШапки(чПоле);
        стр = стр + "
        |        +CASE
        |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
        |        ELSE ''
        |        END
        |";
    КонецЦикла;
    Для чПоле = 1 По тДок.РеквизитТабличнойЧасти() Цикл
        тПоле = тДок.РеквизитТабличнойЧасти(чПоле);
        Если тПоле.ИтогПоКолонке = 1 Тогда
            стр = стр + "
            |        +CASE
            |        WHEN i.SP"+ МД.ИДОбъекта(тПоле) + " <> d.SP"+ МД.ИДОбъекта(тПоле) + " THEN '"+ тПоле.Идентификатор + ",'
            |        ELSE ''
            |        END
            |";
        КонецЕсли;
    КонецЦикла;
    стр = стр + "
    |        FROM INSERTED i, DELETED d
    |        WHERE i.IDDOC = d.IDDOC
    |
    |        SELECT @ИД = IDDOC from DELETED
    |
    |        IF @Поля <> ''
    |        BEGIN
    |            INSERT INTO ЖурналИзменений(Тип,Время,Пользователь,ИД,Таблица,Поля)
    |            VALUES('U', GetDate(), @CurrentUser, @ИД, 'Док" + тДок.Идентификатор + "', @Поля)
    |        END
    |
    |        SET NOCOUNT OFF";
    Попытка
        СКЛ.Выполнить(стр);
    Исключение
    КонецПопытки;
КонецЦикла;

Вот и все.

З.Ы. Некоторые таки могут спросить: и что теперь делать? Как связать это логирование с загрузкой из другой базы?

1. Выполняем все требования статьи «Получение данных из внешней базы 1С».

2. Запускаем на той же базе логирование согласно установкам текущей статьи.

3. И спокойно, для каждого загружаемого объекта, добавляем в запросы проверку на его измененность в течении определенного периода. Вот пример получение Кода и Наименования из справочника Фирмы, элементы которого были изменены в период с 01.10.2004:  

 select distinct Спр.Код
, Спр.Наименование
from СпрФирмы Спр
, ЖурналИзменений Ж
where Ж.Время >= '2004-10-01'
  and Ж.Таблица LIKE '%СпрФирмы%'
  and Ж.ИД = Спр.Ссылка
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
© Вахрин Андрей Викторович
credit