Почему не соединяются данные из COM-соединения в СКД и как правильно передать таблицу значений?

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

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

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

Архитектурная схема взаимодействия систем

Прежде чем перейти к разбору ошибок, давайте кратко опишем правильную схему взаимодействия между базами и СКД:

  1. В локальной базе инициализируется COM-соединение с внешней базой.
  2. Во внешней базе выполняется запрос, который выбирает нужные данные.
  3. Результат запроса выгружается в таблицу значений на стороне внешней базы.
  4. Таблица значений сериализуется в строку XML или внутренний формат 1С и передается в локальную базу.
  5. В локальной базе строка десериализуется обратно в объект ТаблицаЗначений.
  6. Полученная таблица передается в процессор компоновки данных как внешний набор данных.
  7. СКД связывает локальный набор данных (например, запрос к текущей базе) и внешний набор данных по ключевым полям.

---

Ошибка 1. Инициализация таблицы значений внутри цикла обхода выборки

Рассмотрим фрагмент кода, в котором разработчики часто совершают грубейшую логическую ошибку при заполнении локальной таблицы значений:


Пока Выборка.Следующий() Цикл;    
    ТЗ = Новый ТаблицаЗначений; 
    ТЗ.Колонки.Добавить("СпидометрВыезда"); 
    ТЗ.Колонки.Добавить("СпидометрВозвращения"); 
    ТЗ.Колонки.Добавить("Водитель1"); 
    ТЗ.Колонки.Добавить("Вод"); 
    
    Строка = ТЗ.Добавить(); 
    Строка.СпидометрВыезда = Выборка.СпидометрВыезда; 
    Строка.СпидометрВозвращения = Выборка.СпидометрВозвращения; 
    Строка.Водитель1 = String(Выборка.Водитель1Description); 
    Строка.Вод = "Вод";
КонецЦикла;

В чем заключается проблема: Обратите внимание на инструкцию ТЗ = Новый ТаблицаЗначений; внутри цикла Пока. На каждом шаге итерации переменная ТЗ инициализируется заново. Это приводит к тому, что все ранее добавленные строки уничтожаются из оперативной памяти. В итоге, после завершения цикла, в таблице значений останется ровно одна строка — самая последняя запись из выборки. Если по этой записи (например, конкретному водителю) не будет совпадений во второй базе, весь отчет СКД окажется пустым.

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

Исправленный вариант кода заполнения таблицы:


// Инициализируем таблицу значений один раз до начала цикла
ТЗ = Новый ТаблицаЗначений; 
ТЗ.Колонки.Добавить("СпидометрВыезда", Новый ОписаниеТипов("Число")); 
ТЗ.Колонки.Добавить("СпидометрВозвращения", Новый ОписаниеТипов("Число")); 
ТЗ.Колонки.Добавить("Водитель1", Новый ОписаниеТипов("Строка")); 
ТЗ.Колонки.Добавить("Вод", Новый ОписаниеТипов("Строка")); 

Пока Выборка.Следующий() Цикл
    // Добавляем строки в существующую таблицу значений
    Строка = ТЗ.Добавить(); 
    Строка.СпидометрВыезда = Выборка.СпидометрВыезда; 
    Строка.СпидометрВозвращения = Выборка.СпидометрВозвращения; 
    Строка.Водитель1 = Строка(Выборка.Водитель1Description); 
    Строка.Вод = "Вод";
КонецЦикла;

---

Ошибка 2. Попытка прямой сериализации COM-объекта выборки

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


СтрокаРезультат = Соединение.ValueToStringInternal(Выборка); 
ТабВнешняя = ЗначениеИзСтрокиВнутр(СтрокаРезультат);

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

Как исправить: Передавать между базами необходимо физическую таблицу значений, предварительно выгруженную из результата запроса на стороне COM-соединения. Более того, при передаче объектов через COM-границу крайне важно использовать только простые типы данных (числа, строки, даты, булево).

Если вы попытаетесь передать ссылку на справочник (например, СправочникСсылка.ФизическиеЛица), то в локальной базе этот объект превратится в пустую COM-ссылку, так как уникальные идентификаторы (GUID) элементов в разных базах не совпадают, а метаданные базы-приемника ничего не знают о метаданных внешней базы.

Рассмотрим правильный алгоритм выгрузки и сериализации данных через COM:


// 1. Выполняем запрос во внешней базе и сразу выгружаем в таблицу значений (на стороне COM)
ТаблицаНаСторонеCOM = Запрос.Выполнить().Выгрузить();

// 2. Сериализуем полученную таблицу значений в строку внутреннего формата 1С
СтрокаРезультат = Соединение.ValueToStringInternal(ТаблицаНаСторонеCOM); 

// 3. Десериализуем строку в локальной базе, получая полноценную Таблицу Значений
ТабВнешняя = ЗначениеИзСтрокиВнутр(СтрокаРезультат);

---

Ошибка 3. Нюансы соединения данных в СКД по строковым полям

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

А. Разная длина строк и концевые пробелы. В базах данных строковые поля часто имеют фиксированную длину (например, в СУБД используется тип данных CHAR вместо VARCHAR). В этом случае строка дополняется пробелами справа до максимальной длины. Для визуального восприятия строки "Иванов И.И." и "Иванов И.И. " выглядят одинаково, но с точки зрения платформы 1С и СКД — это абсолютно разные строки, и связь по ним не установится.

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

СократитьЛП(Водитель1) = СократитьЛП(ВодительЛокальный)

Б. Использование типа данных "Произвольный". Если при создании колонок в таблице значений программно не ограничить типы данных с помощью класса ОписаниеТипов, то колонкам по умолчанию будет присвоен тип Произвольный. При передаче такой таблицы в СКД, компоновщик не сможет построить эффективный индекс по полю связи, что приведет к игнорированию сопоставления строк.

Решение: Всегда явно описывайте типы колонок при программном создании таблицы значений:

ТЗ.Колонки.Добавить("Водитель1", Новый ОписаниеТипов("Строка"));

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

Также помните, что наложение глобальных отборов компоновщика на поля из подчиненного набора данных неявно преобразует левое соединение во Внутреннее соединение. Если в отборе указан конкретный водитель из внешней таблицы, а связь по нему не отработала из-за пробелов или регистра символов, весь отчет выведется пустым.

---

Пошаговый пример реализации обмена и вывода в СКД

Давайте объединим все правила и напишем эталонный код для получения внешней таблицы значений и ее последующей передачи в схему компоновки данных.

Шаг 1. Напишем функцию получения таблицы значений через COM-соединение:


Функция ПолучитьВнешниеДанныеЧерезCOM(ПараметрыПодключения) Экспорт
    
    // Инициализируем COM-соединение
    Попытка
        Соединение = Новый COMОбъект("v83.COMConnector");
        БазаCOM = Соединение.Connect(ПараметрыПодключения);
    Исключение
        ЗаписатьЛогСобытий("Ошибка подключения к внешней базе: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;
    
    // Создаем запрос во внешней базе
    ЗапросCOM = БазаCOM.NewObject("Query");
    ЗапросCOM.Text = 
    "ВЫБРАТЬ
    |	ПутевыеЛисты.СпидометрВыезда КАК СпидометрВыезда,
    |	ПутевыеЛисты.СпидометрВозвращения КАК СпидометрВозвращения,
    |	ВЫРАЗИТЬ(ПутевыеЛисты.Водитель.Наименование КАК СТРОКА(150)) КАК ВодительФИО
    |ИЗ
    |	Документ.ПутевойЛист КАК ПутевыеЛисты
    |ГДЕ
    |	ПутевыеЛисты.Проведен";
    
    // Выгружаем результат в Таблицу Значений во внешней базе
    ТаблицаCOM = ЗапросCOM.Execute().Unload();
    
    // Сериализуем таблицу во внутренний XML-формат 1С на стороне COM-соединения
    СтрокаДанных = БазаCOM.ValueToStringInternal(ТаблицаCOM);
    
    // Десериализуем данные в локальной базе
    ЛокальнаяТЗ = ЗначениеИзСтрокиВнутр(СтрокаДанных);
    
    Возврат ЛокальнаяТЗ;
    
КонецФункции

Шаг 2. Передадим полученную таблицу значений в СКД при формировании отчета:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    // Получаем внешние данные
    ПараметрыПодключения = "Srvr=""Server1C"";Ref=""TradeDB"";Usr=""Администратор"";Pwd=""123"";";
    ВнешняяТаблица = ПолучитьВнешниеДанныеЧерезCOM(ПараметрыПодключения);
    
    Если ВнешняяТаблица = Неопределено Тогда
        ВызватьИсключение "Не удалось получить данные из внешней ИБ!";
    КонецЕсли;
    
    // Подготавливаем внешние наборы данных для процессора компоновки
    ВнешниеНаборыДанных = Новый Структура;
    ВнешниеНаборыДанных.Вставить("ТаблицаВнешняяБаза", ВнешняяТаблица);
    
    // Инициализируем компоновку
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки);
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
    
КонецПроцедуры

Соблюдение этих правил гарантирует стабильную передачу данных между информационными базами и их корректное сопоставление в схемах любой сложности.

← На главную