Почему СКД выводит количество строк вместо сумм при получении данных из другой базы через COM?

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке сложных аналитических отчетов в 1С программисты часто сталкиваются с необходимостью сопоставления данных из разных информационных баз. Одним из наиболее популярных способов решения этой задачи является использование COM-соединения (COMConnector) для выполнения запроса во внешней базе с последующей передачей результатов в текущую базу через внешний набор данных СКД (Набор данных - объект).

Однако при реализации этого механизма можно столкнуться с неприятным поведением Системы компоновки данных: вместо ожидаемых сумм взаиморасчетов или оборотов в отчете выводится количество строк. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем внутренние механизмы СКД и COM, а также рассмотрим наиболее надежные и быстрые способы передачи данных между информационными базами.

Природа проблемы: почему СКД подменяет суммы количеством строк

Когда мы передаем таблицу значений во внешний набор данных СКД, система должна четко понимать типы данных каждой колонки. Если СКД обнаруживает неопределенный тип или тип Произвольный в колонке, которая назначена ресурсом, она не может корректно применить агрегатную функцию СУММА(). Вместо этого СКД на уровне группировок может автоматически заменить суммирование подсчетом количества записей.

Выделим две основные причины возникновения этой проблемы:

  1. Несоответствие полей в Наборе данных - объекте: Если в схеме компоновки данных (вкладка "Наборы данных") вручную описаны не все поля, которые присутствуют во входящей таблице значений, или если имена полей (колонок) в коде не совпадают с именами в СКД, система компоновки не сможет сопоставить данные. В результате значения ресурсов могут сброситься или рассчитаться некорректно.
  2. Отсутствие жесткой типизации колонок Таблицы Значений: При динамическом получении таблицы значений (например, методом Выгрузить() из COM-запроса) типы колонок могут определиться неявно. При десериализации через функцию ЗначениеИзСтрокиВнутр() в принимающей базе типы колонок часто преобразуются в Неопределено или обобщенный тип, что делает невозможным их корректную обработку математическими функциями СКД.

Давайте рассмотрим, как исправить эти ошибки и какие архитектурные подходы защитят ваш код от подобных сбоев в будущем.

Опасности использования функций ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр

В исходном коде автора темы для передачи таблицы значений из COM-базы используется связка функций ЗначениеВСтрокуВнутр() на стороне источника и ЗначениеИзСтрокиВнутр() на стороне приемника. Разберем, почему этот подход считается устаревшим и крайне опасным:

Проанализируем альтернативные, более надежные варианты построения обмена данными.

Вариант 1. Построчный обход выборки с жесткой типизацией (Рекомендуемый способ)

Самым производительным и безопасным способом является отказ от выгрузки результата запроса в таблицу значений на стороне 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/внутреннюю строку и её последующий парсинг. Кроме того, на этапе заполнения вы можете выполнить поиск реальных ссылок справочников текущей базы по коду или ИНН.

Вариант 2. Сериализация таблицы через XML и XDTO

Если таблица данных очень большая и построчный обход в цикле создает избыточный сетевой оверхед (особенно при работе через тонкие каналы связи), можно использовать стандартный механизм сериализации 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, Тип("ТаблицаЗначений"));

При использовании этого способа типы колонок (такие как Число или Строка) восстанавливаются полностью корректно, что гарантирует правильное суммирование ресурсов в СКД.

Вариант 3. Передача через массив структур (JSON-сериализация)

Еще один современный и легковесный способ передачи данных — преобразование таблицы в массив структур с последующей сериализацией в JSON. Структуры передают только чистые данные, полностью исключая избыточную системную информацию.

Алгоритм действий выглядит следующим образом:

  1. В базе-источнике выгружаем запрос в таблицу значений.
  2. Переносим данные в массив структур (где ключи структуры — это имена колонок таблицы).
  3. Сериализуем массив в JSON-строку.
  4. В базе-приемнике десериализуем JSON-строку обратно в массив структур и загружаем его в типизированную таблицу значений.

Посмотрим на пример кода десериализации в базе-приемнике:


// Предположим, что из COM-базы мы получили СтрокаJSON
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);

МассивСтрок = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();

// Заполняем локальную типизированную таблицу значений
Для Каждого ЭлементСтруктуры Из МассивСтрок Цикл
    НоваяСтрока = ЛокальнаяТЗ.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, ЭлементСтруктуры);
КонецЦикла;

Резюме и чек-лист для проверки отчета

Если ваш отчет СКД, получающий данные из другой базы, выводит количество строк вместо сумм, выполните следующие шаги для диагностики и устранения ошибки:

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

← На главную