При разработке сложных аналитических отчетов в 1С программисты часто сталкиваются с необходимостью сопоставления данных из разных информационных баз. Одним из наиболее популярных способов решения этой задачи является использование COM-соединения (COMConnector) для выполнения запроса во внешней базе с последующей передачей результатов в текущую базу через внешний набор данных СКД (Набор данных - объект).
Однако при реализации этого механизма можно столкнуться с неприятным поведением Системы компоновки данных: вместо ожидаемых сумм взаиморасчетов или оборотов в отчете выводится количество строк. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем внутренние механизмы СКД и COM, а также рассмотрим наиболее надежные и быстрые способы передачи данных между информационными базами.
Когда мы передаем таблицу значений во внешний набор данных СКД, система должна четко понимать типы данных каждой колонки. Если СКД обнаруживает неопределенный тип или тип Произвольный в колонке, которая назначена ресурсом, она не может корректно применить агрегатную функцию СУММА(). Вместо этого СКД на уровне группировок может автоматически заменить суммирование подсчетом количества записей.
Выделим две основные причины возникновения этой проблемы:
Выгрузить() из COM-запроса) типы колонок могут определиться неявно. При десериализации через функцию ЗначениеИзСтрокиВнутр() в принимающей базе типы колонок часто преобразуются в Неопределено или обобщенный тип, что делает невозможным их корректную обработку математическими функциями СКД.Давайте рассмотрим, как исправить эти ошибки и какие архитектурные подходы защитят ваш код от подобных сбоев в будущем.
В исходном коде автора темы для передачи таблицы значений из COM-базы используется связка функций ЗначениеВСтрокуВнутр() на стороне источника и ЗначениеИзСтрокиВнутр() на стороне приемника. Разберем, почему этот подход считается устаревшим и крайне опасным:
<Объект не найден...> в базе-приемнике. Передавать необходимо исключительно простые типы (строки, числа, даты, булево).ЗначениеВСтрокуВнутр() используйте ValueToStringInternal(), а вместо ЗначениеИзСтрокиВнутр() — ValueFromStringInternal().Проанализируем альтернативные, более надежные варианты построения обмена данными.
Самым производительным и безопасным способом является отказ от выгрузки результата запроса в таблицу значений на стороне COM-соединения. Вместо этого мы можем передать в принимающую базу сам объект выборки и заполнить локальную, предварительно типизированную таблицу значений.
Рассмотрим пример реализации этого подхода по шагам:
// 1. Создаем локальную Таблицу Значений с явным указанием типов
ЛокальнаяТЗ = Новый ТаблицаЗначений;
// Задаем типы колонок явно через ОписаниеТипов
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ОписаниеТипаСтрока = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150));
ЛокальнаяТЗ.Колонки.Добавить("Счет", ОписаниеТипаСтрока);
ЛокальнаяТЗ.Колонки.Добавить("Организация", ОписаниеТипаСтрока);
ЛокальнаяТЗ.Колонки.Добавить("Контрагент", ОписаниеТипаСтрока);
ЛокальнаяТЗ.Колонки.Добавить("ИНН", ОписаниеТипаСтрока);
ЛокальнаяТЗ.Колонки.Добавить("СуммаНачальныйОстаток", ОписаниеТипаЧисло);
ЛокальнаяТЗ.Колонки.Добавить("СуммаОборот", ОписаниеТипаЧисло);
ЛокальнаяТЗ.Колонки.Добавить("СуммаКонечныйОстаток", ОписаниеТипаЧисло);
// 2. Выполняем запрос в базе-источнике через COM
ЗапросCOM = Соединение.NewObject("Запрос");
ЗапросCOM.Текст =
"ВЫБРАТЬ
| ПРЕДСТАВЛЕНИЕССЫЛКИ(ХозрасчетныйОстаткиИОбороты.Счет) КАК Счет,
| ПРЕДСТАВЛЕНИЕССЫЛКИ(ХозрасчетныйОстаткиИОбороты.Организация) КАК Организация,
| ПРЕДСТАВЛЕНИЕССЫЛКИ(ХозрасчетныйОстаткиИОбороты.Субконто1) КАК Контрагент,
| ХозрасчетныйОстаткиИОбороты.Субконто1.ИНН КАК ИНН,
| СУММА(ХозрасчетныйОстаткиИОбороты.СуммаНачальныйОстаток) КАК СуммаНачальныйОстаток,
| СУММА(ХозрасчетныйОстаткиИОбороты.СуммаОборот) КАК СуммаОборот,
| СУММА(ХозрасчетныйОстаткиИОбороты.СуммаКонечныйОстаток) КАК СуммаКонечныйОстаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, , , Счет.Код = ""361"", , ) КАК ХозрасчетныйОстаткиИОбороты
|СГРУППИРОВАТЬ ПО
| ХозрасчетныйОстаткиИОбороты.Счет,
| ХозрасчетныйОстаткиИОбороты.Организация,
| ХозрасчетныйОстаткиИОбороты.Субконто1,
| ХозрасчетныйОстаткиИОбороты.Субконто1.ИНН";
ЗапросCOM.УстановитьПараметр("НачалоПериода", ПериодНачало);
ЗапросCOM.УстановитьПараметр("КонецПериода", ПериодОкончания);
// Получаем выборку результатов запроса (объект остается в COM-базе)
ВыборкаCOM = ЗапросCOM.Выполнить().Выбрать();
// 3. Обходим выборку и заполняем нашу строго типизированную таблицу
Пока ВыборкаCOM.Следующий() Цикл
НоваяСтрока = ЛокальнаяТЗ.Добавить();
// Переносим значения простых типов
НоваяСтрока.Счет = ВыборкаCOM.Счет;
НоваяСтрока.Организация = ВыборкаCOM.Организация;
НоваяСтрока.Контрагент = ВыборкаCOM.Контрагент;
НоваяСтрока.ИНН = ВыборкаCOM.ИНН;
// Преобразуем числа во избежание потери типа при маршалинге COM
НоваяСтрока.СуммаНачальныйОстаток = Число(ВыборкаCOM.СуммаНачальныйОстаток);
НоваяСтрока.СуммаОборот = Число(ВыборкаCOM.СуммаОборот);
НоваяСтрока.СуммаКонечныйОстаток = Число(ВыборкаCOM.СуммаКонечныйОстаток);
КонецЦикла;
Этот метод работает быстрее, так как платформа не тратит процессорное время и оперативную память на тяжелую сериализацию всей таблицы в XML/внутреннюю строку и её последующий парсинг. Кроме того, на этапе заполнения вы можете выполнить поиск реальных ссылок справочников текущей базы по коду или ИНН.
Если таблица данных очень большая и построчный обход в цикле создает избыточный сетевой оверхед (особенно при работе через тонкие каналы связи), можно использовать стандартный механизм сериализации XDTO. Платформа 1С умеет штатно преобразовывать ТаблицаЗначений в XML-структуру и обратно без привязки к GUID конкретной базы.
Рассмотрим, как сериализовать таблицу значений на стороне источника:
// Код выполняется в контексте COM-соединения (база-источник)
comТЗ = ЗапросРасчетыСКлиентами.Выполнить().Выгрузить();
// Записываем таблицу значений в XML-строку через СериализаторXDTO
ЗаписьXML = Соединение.NewObject("ЗаписьXML");
ЗаписьXML.УстановитьСтроку();
Соединение.СериализаторXDTO.ЗаписатьXML(ЗаписьXML, comТЗ);
СтрокаXMLДанных = ЗаписьXML.Закрыть();
Теперь десериализуем полученную строку на стороне принимающей базы (приемника):
// Читаем XML-строку в локальном контексте приемника
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXMLДанных);
// Восстанавливаем таблицу значений
ТаблицаРезультат = СериализаторXDTO.ПрочитатьXML(ЧтениеXML, Тип("ТаблицаЗначений"));
При использовании этого способа типы колонок (такие как Число или Строка) восстанавливаются полностью корректно, что гарантирует правильное суммирование ресурсов в СКД.
Еще один современный и легковесный способ передачи данных — преобразование таблицы в массив структур с последующей сериализацией в JSON. Структуры передают только чистые данные, полностью исключая избыточную системную информацию.
Алгоритм действий выглядит следующим образом:
Посмотрим на пример кода десериализации в базе-приемнике:
// Предположим, что из COM-базы мы получили СтрокаJSON
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивСтрок = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Заполняем локальную типизированную таблицу значений
Для Каждого ЭлементСтруктуры Из МассивСтрок Цикл
НоваяСтрока = ЛокальнаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ЭлементСтруктуры);
КонецЦикла;
Если ваш отчет СКД, получающий данные из другой базы, выводит количество строк вместо сумм, выполните следующие шаги для диагностики и устранения ошибки:
СУММА(СуммаОборот)).ЗначениеВСтрокуВнутр() для межсерверного обмена. Перейдите на построчный обход выборки или XML/JSON-сериализацию.ОписаниеТипов. Передавайте только примитивные типы (числа, строки, даты), избегайте прямой передачи ссылочных типов через COM.Применение этих простых правил гарантирует стабильную работу ваших отчетов, быструю скорость формирования и корректность отображения всех финансовых показателей.