gifts2017

1С Предприятие 8.2: Обмен данными между базами на основе инструментов SQL

Опубликовал Alexei Zhovner (jan27) в раздел Обмен - Обмен с другими системами

Обмен данными (сотрудники организаций) между базами (1С Предприятие 8.2: Бухгалтерия и 1С Предприятие 8.2: Зарплата и управление персоналом) на основе триггера SQL, отслеживающего изменения таблицы

Задача: организовать обмен данными между базами без использования промежуточных файлов и COM подключения к 1С приложению. Желательно использовать инструменты SQL. 

Источники:

  1. Отслеживаем изменения в таблице с помощью триггеров Transact SQL
  2. ADODB: Прямая запись и другие операции с SQL - таблицами (MS SQL server 2000)
  3. Структура хранения базы данных

 Обмениваться необходимо данными по сотрудникам организации из базы ЗиУП. При помощи обработки "Структура хранения БД" находим нужную нам таблицу SQL. После этого необходимо повесить на неё триггер, регистрирующий изменения и записывающий эти изменения в отдельную таблицу в отдельной базе. После этого при помощи Views отслеживаем и фильтруем необходимы изменения и записываем в базу.

 

Создание базы и таблицы.

Итак, приступим. Нам понадобится отдельная база.

Create DataBase ExchangeDB
Alter DataBase ExchangeDB SET Recovery Simple
Alter DataBase ExchangeDB SET Auto_Shrink ON

В этой базе создадим таблицу:

USE [ExchangeDB]
GO
/****** Object: Table [dbo].[_Employee_Changes] Script Date: 11/05/2013 10:10:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[_Employee_Changes](
[_IDRRef] [binary](16) NOT NULL,
[_ChangeType] [nvarchar](10) NOT NULL,
[_Unload] [datetime] NOT NULL,
[_Load] [datetime] NULL,
[_Code] [nchar](10) NULL,
[_Description] [nvarchar](100) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

Поясню некоторые поля таблицы. Поле "_ChangeType" предназначено для записи типа изменения ("inserted", "delited", "updated"). Поле "_Unload" показывает время изменения. Поле "_Load" используется для записи времени загрузки изменения (для разных баз можно использовать отдельное поле). Результат показан на рис. 1

Рис. 1

Теперь можно повесить триггер, регистрирующий изменения таблицы "Сотрудники организаций"


Создание триггера SQL.

Я повесил Job такого содержания:

USE [Zup]
GO
/****** Object: Trigger [dbo].[_Employee_changes_trigger] Script Date: 10/28/2013 12:00:16 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--====================================
if exists (select * from sys.triggers where name ='_Employee_changes_trigger')
drop trigger _Employee_changes_trigger
Go
Create trigger [dbo].[_Employee_changes_trigger]
on [Zup].[dbo].[_Reference124] FOR INSERT, UPDATE, DELETE NOT FOR REPLICATION
as
-- SET NOCOUNT ON добавлен чтобы не было лишних результатов выполнения операции
set NOCOUNT ON;

-- определеяем тип произошедших изменений INSERT,UPDATE, or DELETE
declare @change_type as varchar(10)
declare @count as int
set @change_type = 'inserted'
select @count = COUNT(*) FROM DELETED
if @count > 0
  begin
       set@change_type = 'deleted'
        select @count = COUNT(*) from INSERTED
        if @Count > 0
        set @change_type = 'updated'
  end
-- обработка удаления
if @change_type = 'deleted'
  begin
        insert into ExchangeDB.dbo._Employee_Changes(
 _IDRRef, 
_ChangeType,
_Unload,
_Load,
_Code,
_Description
) select
_IDRRef,
'delited',
GETDATE(),
NULL,
_Code,
_Description from deleted
end
else
  begin
-- триггер не различает вставку и удаление, так что добавим ручную обработку
-- обработка вставки
if @change_type = 'inserted'
  begin
    insert into ExchangeDB.dbo._Employee_Changes( 
_IDRRef, 
_ChangeType,
_Unload,
_Load,
_Code,
_Description
) select
_IDRRef,
'inserted',
GETDATE(),
NULL,
_Code,
_Description from inserted
end
-- обработка обновления
else
  begin
    insert into ExchangeDB.dbo._Employee_Changes(
 _IDRRef, 
_ChangeType,
_Unload,
_Load,
_Code,
_Description
) select
_IDRRef,
'updated',
GETDATE(),
NULL,
_Code,
_Description from inserted
end
end -- завершение if
-- завершение 

 В соответствующей таблице должен появитьяс триггер, как показано на рис. 2

Рис. 2

Результат работы триггера представлен на рисунке 3.

Рис. 3

 Создание Views

Далее создаем View таблицы "Сотрудники организации" с необходимыми нам полями (далее разбор на конкретном примере со своей нетиповой спецификой):

USE [ExchangeDB]
GO
/****** Object: View [dbo].[V_Reference124] Script Date: 11/05/2013 11:48:42 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[V_Reference124]
AS
SELECT ZuP.dbo._Reference124._IDRRef, ZuP.dbo._Reference124._Marked, ZuP.dbo._Reference124._Code, ZuP.dbo._Reference124._Description,
ZuP.dbo._Reference53._Description AS CurrentPosition, ZuP.dbo._Reference124._Fld1812 AS HiringDate, ZuP.dbo._Reference124._Fld1813 AS TerminationDate,
ZuP.dbo._Reference124._Fld9932 AS DriverLicence, ZuP.dbo._Reference9960._Code AS GarageCode, ZuP.dbo._Reference9960._Description AS Garage
FROM ZuP.dbo._Reference124 LEFT OUTER JOIN
ZuP.dbo._Reference9960 ON ZuP.dbo._Reference124._Fld10102RRef = ZuP.dbo._Reference9960._IDRRef LEFT OUTER JOIN
ZuP.dbo._Reference53 ON ZuP.dbo._Reference124._Fld1811RRef = ZuP.dbo._Reference53._IDRRef
GO

Создаём View для таблицы изменений (различные добавленные линейные водители):

USE [ExchangeDB]
GO
/****** Object: View [dbo].[V_Distinct_Employee_ChangesLD_Inserted] Script Date: 11/05/2013 13:58:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[V_Distinct_Employee_ChangesLD_Inserted]
AS
SELECT dbo.V_Reference124._IDRRef, dbo.V_Reference124._Marked, dbo.V_Reference124._Code, dbo.V_Reference124._Description, dbo.V_Reference124.CurrentPosition,
dbo.V_Reference124.HiringDate, dbo.V_Reference124.TerminationDate, dbo.V_Reference124.DriverLicence, dbo.V_Reference124.GarageCode,
dbo.V_Reference124.Garage
FROM (SELECT DISTINCT _IDRRef
FROM dbo._Employee_Changes
WHERE (_ChangeType = 'inserted') AND (_Load IS NULL)) AS Changes LEFT OUTER JOIN
dbo.V_Reference124 ON Changes._IDRRef = dbo.V_Reference124._IDRRef
WHERE (dbo.V_Reference124.CurrentPosition = N'Линейный водитель')
GO

Создаем View для таблицы изменений (различные обновленные линейные водители)

USE [ExchangeDB]
GO
/****** Object: View [dbo].[V_Distinct_Employee_ChangesLD_Updated] Script Date: 11/05/2013 14:12:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[V_Distinct_Employee_ChangesLD_Updated]
AS
SELECT dbo.V_Reference124._IDRRef, dbo.V_Reference124._Marked, dbo.V_Reference124._Code, dbo.V_Reference124._Description, dbo.V_Reference124.CurrentPosition,
dbo.V_Reference124.HiringDate, dbo.V_Reference124.TerminationDate, dbo.V_Reference124.DriverLicence, dbo.V_Reference124.GarageCode,
dbo.V_Reference124.Garage
FROM (SELECT DISTINCT _IDRRef
FROM dbo._Employee_Changes
WHERE (_ChangeType = 'updated') AND (_Load IS NULL)) AS Changes LEFT OUTER JOIN
dbo.V_Reference124 ON Changes._IDRRef = dbo.V_Reference124._IDRRef
WHERE (dbo.V_Reference124.CurrentPosition = N'Линейный водитель')
GO

Таким образом, мы получили 3 Views (рис.4)

Рис. 4

Данные View по изменениям сотрудников представлены на рис. 5

Рис. 5

 

Регламентное задание

В базе, которая будет получать данные создаем регламентное задание, в общем модуле регламентного задания размещаем следующий код:

//SQL загрузка
//************Функции***************

Функция ВернутьЗначениеИзМассиваБулево(СомМассив)
   
Массив = Новый Массив();
   
Массив = СомМассив.Выгрузить();
   
Пометка = Массив.Получить(0);
    Если
Пометка = 0 тогда
        Возврат Ложь;
    Иначе
        Возврат Истина;
    КонецЕсли;
КонецФункции

Функция
ПреобразоватьДату(ДатаПреобразования)
    Возврат
Дата(Число(Сред(ДатаПреобразования,7,4))-2000,Сред(ДатаПреобразования,4,2),Лев(ДатаПреобразования,2));
КонецФункции

//************Процедуры*************
Процедура ТМ_ЗагрузкаВодителейСКЛ() Экспорт
   
СтрокаКоннекта = "Provider=SQLOLEDB;Password=*****;Persist Security Info=True;User ID=ExchangeDBwriter;Initial Catalog=ExchangeDB;Data Source=localhost";
   
Connection = Новый COMОбъект("ADODB.Connection");
   
Command = Новый COMObject("ADODB.Command");
   
RecordSet = Новый COMObject("ADODB.RecordSet");
    
Connection.ConnectionString = СтрокаКоннекта;
   
Connection.ConnectionTimeOut = 120;
   
Connection.CursorLocation = 3;
   
Connection.IsolationLevel = 4096;
    
ТЗ = Новый ТаблицаЗначений;
   
ТЗ.Колонки.Добавить("Код");
   
ТЗ.Колонки.Добавить("ПометкаУдаления");
   
ТЗ.Колонки.Добавить("Наименование");
   
ТЗ.Колонки.Добавить("ДатаПриема");
   
ТЗ.Колонки.Добавить("ДатаУвольнения");
   
ТЗ.Колонки.Добавить("НомерЛицензии");
   
ТЗ.Колонки.Добавить("Гараж");
    Попытка
       
Connection.Open();
    Исключение
        Возврат;
    КонецПопытки;
    
//сначала загружаем новые элементы
   
Text="select * from V_Distinct_Employee_ChangesLD_Inserted";
   
Command.ActiveConnection = Connection;
   
Command.CommandTimeout = 120;
   
Command.CommandText = Text;
    Попытка
       
RecordSet.Open(Command);
    Исключение
       
Connection.Close();
        Возврат;
    КонецПопытки;
    Пока НЕ (
RecordSet.EOF) Цикл
       
НоваяСтрока = ТЗ.Добавить();
        
НоваяСтрока.Код = RecordSet.Fields("_Code").Value();
       
ПометкаУдаления = ВернутьЗначениеИзМассиваБулево(RecordSet.Fields("_Marked").Value());
       
НоваяСтрока.ПометкаУдаления = ПометкаУдаления;
       
НоваяСтрока.Наименование = RecordSet.Fields("_Description").Value();
       
НоваяСтрока.ДатаПриема = ПреобразоватьДату(RecordSet.Fields("HiringDate").Value());
       
НоваяСтрока.ДатаУвольнения = ПреобразоватьДату(RecordSet.Fields("TerminationDate").Value());
       
НоваяСтрока.НомерЛицензии = RecordSet.Fields("DriverLicence").Value();
       
НоваяСтрока.Гараж = Справочники.ТМ_Гаражи.НайтиПоНаименованию(RecordSet.Fields("Garage").Value());
        
RecordSet.MoveNext();
    КонецЦикла;
   
Recordset.Close();

   
//записываем данные в базу
   
Для каждого стр из тз цикл
       
//ищем сотрудника
       
ВодительСсылка = Справочники.ТМ_Водители.НайтиПоКоду(стр.Код);
        Если
ВодительСсылка = Справочники.ТМ_Водители.ПустаяСсылка() тогда
           
ВодительОбъект = Справочники.ТМ_Водители.СоздатьЭлемент();
        Иначе
           
ВодительОбъект = ВодительСсылка.ПолучитьОбъект();
        КонецЕсли;
       
ВодительОбъект.Код = стр.Код;
       
ВодительОбъект.Наименование = стр.Наименование;
       
ВодительОбъект.ПометкаУдаления = стр.ПометкаУдаления;
       
ВодительОбъект.ДатаПриема = стр.ДатаПриема;
       
ВодительОбъект.ДатаУвольнения = стр.ДатаУвольнения;
       
ВодительОбъект.НомерУдостоверенияВодителя = стр.НомерЛицензии;
       
ВодительОбъект.Гараж = стр.Гараж;
        Попытка
           
ВодительОбъект.Записать();
           
//отмечаем загруженных
           
CommandText = "UPDATE _Employee_Changes Set _Load = GetDate() where _Code='"+стр.Код+"' and _Load is NULL";
           
Connection.Execute(CommandText,,128);
        Исключение
            Продолжить;
        КонецПопытки;
    КонецЦикла;

   
//теперь пишем обновленных
   
тз.Очистить();
   
Text="select * from V_Distinct_Employee_ChangesLD_Updated";
   
Command.CommandText = Text;
    Попытка
       
RecordSet.Open(Command);
    Исключение
       
Connection.Close();
        Возврат;
    КонецПопытки;
    Пока НЕ (RecordSet.EOF) Цикл
        НоваяСтрока = ТЗ.Добавить();
        
НоваяСтрока.Код = RecordSet.Fields("_Code").Value();
       
ПометкаУдаления = ВернутьЗначениеИзМассиваБулево(RecordSet.Fields("_Marked").Value());
       
НоваяСтрока.ПометкаУдаления = ПометкаУдаления;
       
НоваяСтрока.Наименование = RecordSet.Fields("_Description").Value();
       
НоваяСтрока.ДатаПриема = ПреобразоватьДату(RecordSet.Fields("HiringDate").Value());
       
НоваяСтрока.ДатаУвольнения = ПреобразоватьДату(RecordSet.Fields("TerminationDate").Value());
       
НоваяСтрока.НомерЛицензии = RecordSet.Fields("DriverLicence").Value();
       
НоваяСтрока.Гараж = Справочники.ТМ_Гаражи.НайтиПоНаименованию(RecordSet.Fields("Garage").Value());
        
RecordSet.MoveNext();
    КонецЦикла;
   
Recordset.Close();

   
//записываем данные в базу
   
Для каждого стр из тз цикл
       
//ищем сотрудника
       
ВодительСсылка = Справочники.ТМ_Водители.НайтиПоКоду(стр.Код);
        Если
ВодительСсылка = Справочники.ТМ_Водители.ПустаяСсылка() тогда
           
ВодительОбъект = Справочники.ТМ_Водители.СоздатьЭлемент();
        Иначе
           
ВодительОбъект = ВодительСсылка.ПолучитьОбъект();
        КонецЕсли;
       
ВодительОбъект.Код = стр.Код;
       
ВодительОбъект.Наименование = стр.Наименование;
       
ВодительОбъект.ПометкаУдаления = стр.ПометкаУдаления;
       
ВодительОбъект.ДатаПриема = стр.ДатаПриема;
       
ВодительОбъект.ДатаУвольнения = стр.ДатаУвольнения;
       
ВодительОбъект.НомерУдостоверенияВодителя =?(ЗначениеЗаполнено(ВодительОбъект.НомерУдостоверенияВодителя),ВодительОбъект.НомерУдостоверенияВодителя,стр.НомерЛицензии);
       
ВодительОбъект.Гараж = стр.Гараж;
        Попытка
           
ВодительОбъект.Записать();
           
//отмечаем загруженных
           
CommandText = "UPDATE _Employee_Changes Set _Load = GetDate() where _Code='"+стр.Код+"'  and _Load is NULL";
           
Connection.Execute(CommandText,,128);
        Исключение
            Продолжить;
        КонецПопытки;
    КонецЦикла;
    
Connection.Close();
КонецПроцедуры
//SQL загрузка

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Vlad Matveev (psamt1k) 06.11.13 15:14
Изменения табличных частей, полагаю, так тоже можно сделать?
ЗЫ: поправьте пожалуйста первую ссылку, нерабочая.
2. Alexei Zhovner (jan27) 06.11.13 17:03
(1)да. у меня ссылка работает
Прикрепленные файлы:
3. Дмитрий Уточкин (EdmundoAlvares) 06.11.13 21:51
4. Василий Казьмин (awk) 06.11.13 23:13
(0) Все хорошо, но код на 1С делает все ненужным. Его нужно свести к:

Command.CommandText = "SELECT TOP 100
...
FROM ...
FOR XML";
...
RecordSet.Open(Command);
...
Пока НЕ (RecordSet.EOF) Цикл
Объект1С = CериализаторXML.ПрочитатьXML(ЗаписьXML);
Объект1С.Записать();


То есть делаем FOR XML получаем нативный сериализованый в XML формате объект 1С десериализуем и записываем.
zarucheisky; jan27; +2 Ответить 4
5. Alexei Zhovner (jan27) 06.11.13 23:20
(4) с недоверием отношусь к 1С-ому xml
6. Alexei Zhovner (jan27) 06.11.13 23:21
7. script Мальчинко (script) 09.11.13 21:22
(4) awk,
Вы поселили вирус в голове человека.
Он ведь гордился своим решением, даже на инфостарт выложил.
Оно было сложным и технологичным, содержало 300 строк кода на разных языках,
а вы его .... убили своими 10 строчками. Так ведь и комплекс не полноценности развиться может.
Опасно однако ....
8. Василий Казьмин (awk) 09.11.13 23:35
(7) script, Я то же делал такие решения:

1. Кода было много времени и мало знаний.
2. Когда было много знаний и мало времени.

Когда же ситуация типовая:

1. Нет ни времени ни знаний
2. Куча знаний и времени

То надо пользоваться типовыми методами - например КД + БСП.
9. Alexei Zhovner (jan27) 10.11.13 14:51
(8) пока окончательно не развился комплекс неполноценности, просветите что такое БСП?
:)))
10. Ийон Тихий (cool.vlad4) 10.11.13 17:12
(9) jan27, библиотека стандартных подсистем,
jan27; awk; +2 Ответить 1
11. Alexei Zhovner (jan27) 11.11.13 22:45
(10)спасибо, чет не состыковал....)
12. Alexei Zhovner (jan27) 13.11.13 18:04
(4) при использовании "...FOR XML..." RecordSet содержит одно поле и одну строку типа ComSafeArray
13. Василий Казьмин (awk) 13.11.13 22:50
14. Alexei Zhovner (jan27) 14.11.13 05:32
(13) а как вытащить xml оттуда?
15. Сергей Иванов (xten) 19.12.13 17:04
Я правильно понимаю, что такой обмен возможно реализовать между любыми конфигурациями?
16. Alexei Zhovner (jan27) 20.12.13 10:17
(15) да. можно даже с другими базами (не 1С) - главное, чтобы на SQL бегали
17. andrey dyak (dyak84) 30.01.15 12:56
А как зделать с отбором по организации в документах Реализация???????????????Зарание спасибо за ответ
18. Alexei Zhovner (jan27) 30.01.15 16:15
19. hanio (hanio) 01.03.15 19:54
Подскажите пожалуйста а как отключить триггеры?
20. Alexei Zhovner (jan27) 02.03.15 13:21
(19) например, так: drop trigger _Employee_changes_trigger
21. Николай Зевеке (zekrus) 16.04.15 13:39
А что мешает выполнить полный обмен на стороне SQL (не используя идентификаторы базы источника)?
22. Alexei Zhovner (jan27) 16.04.15 15:58
(21) да, собственно, ничего не мешает... а чем Вам идентификаторы помешали?
23. Евгений Заручейский (zarucheisky) 16.04.15 16:35
Проще, для сериализуемых объектов включить CDC ( Change Data Capture), c независимыми РС можно использовать CDT (Change Data Tracking), но осторожно.

Остальное, в принципе, так же и ничего нового. Вплоть до создания view с человеческими именами & табличных функций.
24. d f (hazyaka) 16.07.15 17:45
(4) awk, а есть поподробнее статьи для новичков? не могу дофатазировать что там..