При интеграции различных баз данных 1С программисты часто прибегают к использованию COMОбъект (COM-соединение). Это проверенный временем инструмент, однако он таит в себе множество нюансов, связанных с передачей данных между процессами (маршалингом). Одной из самых коварных проблем является некорректная интерпретация значения NULL, когда при выполнении сложного запроса с объединениями к удаленной базе вместо ожидаемых чисел приходят пустые значения. В этой статье мы подробно разберем, почему так происходит и как правильно составить запрос, чтобы данные передавались без потерь.
Рассмотрим ситуацию: нам необходимо выгрузить из удаленной базы сальдо и обороты по контрагентам. Мы используем запрос к регистру бухгалтерии Хозрасчетный, соединяя таблицы остатков и оборотов. Когда нам требуется добавить еще одну таблицу (например, остатки по счету 10.07 "Материалы, переданные в переработку") через оператор ОБЪЕДИНИТЬ ВСЕ, мы можем столкнуться с тем, что в результирующей выборке на стороне вызывающей базы колонки новой таблицы оказываются пустыми (содержат NULL).
Проблема заключается в том, что COM-соединение чувствительно к типам данных в колонках результата запроса. Если в одной части объединения колонка содержит число, а в другой — NULL, то при передаче этой выборки через COM-интерфейс механизм преобразования типов может дать сбой. Для 1С внутри одной базы это нормальная ситуация, но для внешнего соединения это создает неопределенность.
Проанализируем фрагмент кода, который часто приводит к ошибкам. Обратите внимание на использование NULL в качестве заглушек для колонок во второй части объединения:
Запрос = Connection.NewObject("Запрос");
Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстатокДт, 0) КАК НамДолжны,
| NULL КАК Сальдо_1007
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет В ИЕРАРХИИ (&СписокСчетов), , ) КАК ХозрасчетныйОстатки
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| NULL,
| ЕСТЬNULL(Хозрасчетный_1007.СуммаОстатокДт, 0)
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет = &Счет_1007, , ) КАК Хозрасчетный_1007";
В данном примере мы пытаемся в первой части запроса зарезервировать место под поле Сальдо_1007 с помощью литерала NULL. На стороне вызывающей базы при обходе выборки через Выборка.Следующий() обращение к Выборка.Сальдо_1007 может вернуть NULL даже для тех строк, где во второй части запроса явно есть число. Это происходит из-за того, что колонка не получила жесткой типизации «число» в первой части объединения.
Чтобы гарантировать корректную передачу данных через COM, мы должны привести все числовые показатели к числу в каждой части объединения. Вместо NULL используем 0. Это позволит COM-обертке однозначно определить тип колонки как числовой.
Рассмотрим правильный вариант формирования текста запроса:
Текст =
"ВЫБРАТЬ
| Хозрасчетный_1007.Субконто1.Наименование КАК КонтрагентНаименование,
| Хозрасчетный_1007.Субконто1.Родитель.Наименование КАК РодительНаименование,
| Хозрасчетный_1007.Субконто1.ИНН КАК ИНН,
| 0 КАК НамДолжны,
| 0 КАК МыДолжны,
| 0 КАК Оплата,
| ЕСТЬNULL(Хозрасчетный_1007.СуммаОстатокДт, 0) КАК Сальдо_1007
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет = &Счет_1007, , ) КАК Хозрасчетный_1007";
Заменив NULL на 0, мы избавляемся от неопределенности типа. Теперь при объединении с основной таблицей (где эти поля также числовые) выборка будет иметь четкую структуру, понятную для COM-соединения.
При работе через COM каждое обращение к свойствам объекта «через точку» (например, Выборка.Контрагент.Наименование) инициирует отдельный вызов к удаленной базе. Если в вашей выборке тысячи строк, это приведет к катастрофическому замедлению работы.
Рекомендуется:
ПРЕДСТАВЛЕНИЕ() для ссылочных полей.Если нам все же необходимо работать со ссылками, проанализируем вспомогательные функции, которые помогут правильно обработать данные на принимающей стороне. Рассмотрим метод получения уникального идентификатора объекта из удаленной базы:
Функция ПолучитьГУИД_ЭлементаCOM(COM_Ссылка, БазаИсточник)
Попытка
// Преобразуем УникальныйИдентификатор в строку на стороне источника
Возврат Строка(БазаИсточник.String(COM_Ссылка.УникальныйИдентификатор()));
Исключение
Возврат "";
КонецПопытки;
КонецФункции
Имея строковый ГУИД, мы можем найти соответствующий объект в нашей локальной базе. Это гораздо надежнее и быстрее, чем пытаться передать саму ссылку через COM и сравнивать её напрямую.
Часто задача сводится к тому, чтобы найти контрагента в текущей базе по данным из удаленной. Посмотрим на алгоритм, который минимизирует ошибки сопоставления:
Функция НайтиКонтрагентаПоДаннымCOM(COM_Контрагент)
ИНН = СокрЛП(COM_Контрагент.ИНН);
КПП = СокрЛП(COM_Контрагент.КПП);
Если ПустаяСтрока(ИНН) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ИНН", ИНН);
Запрос.УстановитьПараметр("КПП", КПП);
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И Контрагенты.КПП = &КПП";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат Неопределено;
КонецЕсли;
Выборка = Результат.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецФункции
Подведем итог нашему исследованию работы с COM-запросами:
ОБЪЕДИНИТЬ ВСЕ заменяйте NULL на 0 для чисел и на пустую строку для текстовых полей.Наименование и ИНН сразу в запросе. Помните, что Субконто1.Наименование в запросе — это хорошо, а Выборка.Субконто1.Наименование в цикле — это очень медленно.Запрос.УстановитьПараметр("Счет", ...)), убедитесь, что объект «Счет» получен именно из той же Connection, в которой выполняется запрос.Соблюдение этих правил позволит вам создавать стабильные и быстрые механизмы интеграции, которые не будут «терять» данные при передаче из одной базы 1С в другую.