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

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

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

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

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

Анализ типичных ошибок разработчиков

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

Ошибка 1. Перезапись структуры наборов данных
Часто разработчики пытаются передать таблицы в СКД последовательным объявлением структуры:


НаборыДанных = Новый Структура("ВходящиеДанные", ТЗ);
НаборыДанных = Новый Структура("ВходящиеДанные2", ТЗ2);

В этом коде во второй строке переменная НаборыДанных полностью инициализируется заново. Первая структура с ключом ВходящиеДанные уничтожается, и в процессор компоновки передается структура только с одним набором. СКД закономерно сообщает об ошибке: «Набор данных "ВходящиеДанные" не найден».

Ошибка 2. Опечатки при копировании переменных запроса
При выполнении нескольких запросов через COM легко запутаться в их именах. Рассмотрим этот фрагмент:


Запрос2 = Соединение.NewObject("Запрос");
Запрос.Текст ="ВЫБРАТЬ ..."; // Ошибка! Текст присвоен переменной первого запроса
РезультатЗапроса = Запрос.Выполнить(); 
Таблица2 = Запрос2.Выполнить.Выгрузить(); // Ошибка! Запрос2 пустой

Поскольку свойству Запрос2.Текст не было присвоено значение, метод Выполнить() вызывает системное исключение во внешней базе с текстом: «Ожидается выражение "ВЫБРАТЬ"».

Ошибка 3. Неверное использование метода ЗаполнитьЗначенияСвойств
Попытка объединить две полноценные таблицы значений или их строковые представления с помощью конструкции:

ЗаполнитьЗначенияСвойств(ТЗ, Таблица2);

не даст никакого результата. Метод ЗаполнитьЗначенияСвойств предназначен для копирования одинаковых колонок/свойств между конкретными одиночными объектами (например, строками таблиц), но не умеет соединять таблицы значений по ключевым полям.

Ошибка 4. Передача сложных типов данных через COM
При передаче таблиц через COM-соединение нельзя передавать ссылки на справочники или документы (например, СправочникСсылка.ФизическиеЛица). UUID объектов в разных базах не совпадают, поэтому при десериализации вы получите «битые» ссылки. Передавать необходимо только примитивные типы данных: строки, числа, даты и булево. Все ссылки в запросах внешней базы должны быть сразу преобразованы в строковые представления (например, Водитель.Наименование или Водитель.Код).

---

Решение №1. Связывание двух наборов данных непосредственно в схеме СКД (Рекомендуемый способ)

Самый правильный и декларативный способ — передать обе таблицы в СКД как независимые внешние наборы данных типа «Объект» и настроить связь между ними на уровне самой схемы компоновки. СКД самостоятельно выполнит аналог левого соединения, не допуская дублирования и искажения числовых показателей.

Рассмотрим по шагам процесс реализации этого механизма.

Шаг 1. Настройка схемы компоновки данных (СКД)

  1. Откройте схему компоновки данных вашего отчета.
  2. Перейдите на вкладку «Наборы данных».
  3. Добавьте первый набор данных типа «Набор данных - объект». Назовите его ВходящиеДанные. В поле «Имя объекта содержащего данные» также укажите ВходящиеДанные. Добавьте в него колонки:
    • Наименование (Тип: Строка) — имя водителя;
    • Дата (Тип: Дата) — дата периода;
    • ПройденноеРасстояние (Тип: Число).
  4. Добавьте второй набор данных типа «Набор данных - объект». Назовите его ВходящиеДанные2. Добавьте в него колонки:
    • Наименование2 (Тип: Строка) — имя водителя для связи;
    • Дата2 (Тип: Дата) — дата последнего путевого листа.
  5. Перейдите на вкладку «Связи наборов данных». Добавьте новую строку:
    • Источник связи: ВходящиеДанные;
    • Приемник связи: ВходящиеДанные2;
    • Условие связи: Наименование = Наименование2;
    • Параметры: оставьте пустыми.

Шаг 2. Написание программного кода для получения и передачи данных

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


// Установим COM-соединение с внешней базой данных
ПараметрыПодключения = "Srvr=""ServerName"";Ref=""BaseName"";Usr=""Admin"";Pwd=""Password"";";
Connector = Новый COMОбъект("V83.COMConnector");

Попытка
    Соединение = Connector.Connect(ПараметрыПодключения);
Исключение
    ВызватьИсключение "Не удалось подключиться к внешней базе: " + ОписаниеОшибки();
КонецПопытки;

// --- ЗАПРОС 1: Пройденное расстояние по всем путевым листам ---
Запрос1 = Соединение.NewObject("Запрос");
Запрос1.Текст = 
    "ВЫБРАТЬ
    |	СУММА(уатПутевойЛист.СпидометрВозвращения - уатПутевойЛист.СпидометрВыезда) КАК ПройденноеРасстояние,
    |	НАЧАЛОПЕРИОДА(уатПутевойЛист.Дата, ДЕНЬ) КАК Дата,
    |	уатПутевойЛист.Водитель1.Наименование КАК Наименование
    |ИЗ
    |	Документ.уатПутевойЛист КАК уатПутевойЛист
    |СГРУППИРОВАТЬ ПО
    |	уатПутевойЛист.Водитель1.Наименование,
    |	НАЧАЛОПЕРИОДА(уатПутевойЛист.Дата, ДЕНЬ)";

Результат1 = Запрос1.Выполнить();
ТЗ_Источник1 = Результат1.Выгрузить();

// Сериализуем и десериализуем таблицу, чтобы получить локальный объект ТЗ в нашей базе
СтрокаТЗ1 = Соединение.ЗначениеВСтрокуВнутр(ТЗ_Источник1);
ТЗ1 = ЗначениеИзСтрокиВнутр(СтрокаТЗ1);

// --- ЗАПРОС 2: Дата последнего путевого листа каждого водителя ---
Запрос2 = Соединение.NewObject("Запрос");
Запрос2.Текст = 
    "ВЫБРАТЬ
    |	ПоследниеДокументыВодителей.Дата КАК Дата2,
    |	ПоследниеДокументыВодителей.Водитель1.Наименование КАК Наименование2
    |ИЗ
    |	Документ.уатПутевойЛист КАК ПоследниеДокументыВодителей
    |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
    |		ВЫБРАТЬ
    |			уатПутевойЛист.Водитель1 КАК Водитель1,
    |			МАКСИМУМ(уатПутевойЛист.Дата) КАК Дата
    |		ИЗ
    |			Документ.уатПутевойЛист КАК уатПутевойЛист
    |		СГРУППИРОВАТЬ ПО
    |			уатПутевойЛист.Водитель1
    |	) КАК ДатыПоследнихДокументовВодителей
    |	ПО ПоследниеДокументыВодителей.Водитель1 = ДатыПоследнихДокументовВодителей.Водитель1
    |	И ПоследниеДокументыВодителей.Дата = ДатыПоследнихДокументовВодителей.Дата";

Результат2 = Запрос2.Выполнить();
ТЗ_Источник2 = Результат2.Выгрузить();

// Переносим вторую таблицу во внутренний контекст
СтрокаТЗ2 = Соединение.ЗначениеВСтрокуВнутр(ТЗ_Источник2);
ТЗ2 = ЗначениеИзСтрокиВнутр(СтрокаТЗ2);

// Закрываем COM-соединение, оно нам больше не нужно
Соединение = Неопределено;

// Отключаем стандартный вывод отчета
СтандартнаяОбработка = Ложь;

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

// Инициализируем макет и выводим его на экран
Схема = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
КМ = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КМ.Выполнить(Схема, КомпоновщикНастроек.Настройки);

ПК = Новый ПроцессорКомпоновкиДанных;
ПК.Инициализировать(Макет, НаборыДанных);

ПВ = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПВ.УстановитьДокумент(ДокументРезультат);
ПВ.Вывести(ПК, Истина);

---

Решение №2. Программное объединение таблиц в коде 1С перед выводом

Если по каким-то причинам вы не хотите настраивать связи внутри СКД (например, если требуется сложная программная обработка строк перед выводом или вы хотите использовать ровно один набор данных в схеме), таблицы можно объединить в коде 1С с помощью индексирования по ключевому полю.

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


// 1. Создаем результирующую таблицу на основе первой
ТЗ_Итог = ТЗ1.Скопировать();

// 2. Добавляем в нее колонку для хранения даты последнего документа
ТЗ_Итог.Колонки.Добавить("ДатаПоследнегоДокумента", Новый ОписаниеТипов("Дата"));

// 3. Индексируем вторую таблицу по ключевому полю для максимально быстрого поиска
ТЗ2.Индексы.Добавить("Наименование2");

// 4. Бежим по строкам и сопоставляем данные
Для Каждого СтрокаТЗ Из ТЗ_Итог Цикл
    
    // Подготавливаем структуру отбора по ключевому полю
    ПараметрыОтбора = Новый Структура("Наименование2", СтрокаТЗ.Наименование);
    НайденныеСтроки = ТЗ2.НайтиСтроки(ПараметрыОтбора);
    
    // Если соответствие найдено, переносим данные во вновь созданную колонку
    Если НайденныеСтроки.Количество() > 0 Тогда
        СтрокаТЗ.ДатаПоследнегоДокумента = НайденныеСтроки[0].Дата2;
    КонецЕсли;
    
КонецЦикла;

// 5. Передаем единую объединенную таблицу в СКД
НаборыДанных = Новый Структура("ВходящиеДанные", ТЗ_Итог);

При использовании этого подхода в схеме СКД вам потребуется описать только один набор данных ВходящиеДанные, содержащий поля Наименование, Дата, ПройденноеРасстояние и ДатаПоследнегоДокумента. Этот метод работает быстро даже на больших объемах данных благодаря индексации таблицы приемника.

---

Важные рекомендации по работе с COM-соединением

При обмене данными между базами через COM-объекты полезно придерживаться следующих правил:

← На главную