При разработке сложных отчетов в 1С часто возникает необходимость собрать данные из внешней информационной базы. Для этого обычно используется COM-соединение (V83.COMConnector). Получить один набор данных и передать его в СКД как внешний набор данных типа «Объект» не составляет труда. Но что делать, если нужно получить два независимых запроса из внешней базы и корректно связать их в отчете?
Например, первый запрос собирает общее пройденное расстояние по водителям за весь период, а второй запрос возвращает дату и параметры самого последнего путевого листа по каждому водителю. Если попытаться объединить эти запросы в один во внешней базе, то данные по расстоянию начнут задваиваться или выводиться некорректно. Единственный верный путь — получить две отдельные таблицы значений и связать их уже на стороне СКД нашей текущей базы.
В этой статье мы подробно разберем типичные ошибки, которые допускают разработчики при решении этой задачи, и рассмотрим два надежных способа реализации.
Проанализируем ситуацию и выясним причины распространенных ошибок, из-за которых отчеты выдают пустые результаты или вызывают аварийное завершение работы программы.
Ошибка 1. Перезапись структуры наборов данных
Часто разработчики пытаются передать таблицы в СКД последовательным объявлением структуры:
НаборыДанных = Новый Структура("ВходящиеДанные", ТЗ);
НаборыДанных = Новый Структура("ВходящиеДанные2", ТЗ2);
В этом коде во второй строке переменная НаборыДанных полностью инициализируется заново. Первая структура с ключом ВходящиеДанные уничтожается, и в процессор компоновки передается структура только с одним набором. СКД закономерно сообщает об ошибке: «Набор данных "ВходящиеДанные" не найден».
Ошибка 2. Опечатки при копировании переменных запроса
При выполнении нескольких запросов через COM легко запутаться в их именах. Рассмотрим этот фрагмент:
Запрос2 = Соединение.NewObject("Запрос");
Запрос.Текст ="ВЫБРАТЬ ..."; // Ошибка! Текст присвоен переменной первого запроса
РезультатЗапроса = Запрос.Выполнить();
Таблица2 = Запрос2.Выполнить.Выгрузить(); // Ошибка! Запрос2 пустой
Поскольку свойству Запрос2.Текст не было присвоено значение, метод Выполнить() вызывает системное исключение во внешней базе с текстом: «Ожидается выражение "ВЫБРАТЬ"».
Ошибка 3. Неверное использование метода ЗаполнитьЗначенияСвойств
Попытка объединить две полноценные таблицы значений или их строковые представления с помощью конструкции:
ЗаполнитьЗначенияСвойств(ТЗ, Таблица2);
не даст никакого результата. Метод ЗаполнитьЗначенияСвойств предназначен для копирования одинаковых колонок/свойств между конкретными одиночными объектами (например, строками таблиц), но не умеет соединять таблицы значений по ключевым полям.
Ошибка 4. Передача сложных типов данных через COM
При передаче таблиц через COM-соединение нельзя передавать ссылки на справочники или документы (например, СправочникСсылка.ФизическиеЛица). UUID объектов в разных базах не совпадают, поэтому при десериализации вы получите «битые» ссылки. Передавать необходимо только примитивные типы данных: строки, числа, даты и булево. Все ссылки в запросах внешней базы должны быть сразу преобразованы в строковые представления (например, Водитель.Наименование или Водитель.Код).
---
Самый правильный и декларативный способ — передать обе таблицы в СКД как независимые внешние наборы данных типа «Объект» и настроить связь между ними на уровне самой схемы компоновки. СКД самостоятельно выполнит аналог левого соединения, не допуская дублирования и искажения числовых показателей.
Рассмотрим по шагам процесс реализации этого механизма.
ВходящиеДанные. В поле «Имя объекта содержащего данные» также укажите ВходящиеДанные. Добавьте в него колонки:
Наименование (Тип: Строка) — имя водителя;Дата (Тип: Дата) — дата периода;ПройденноеРасстояние (Тип: Число).ВходящиеДанные2. Добавьте в него колонки:
Наименование2 (Тип: Строка) — имя водителя для связи;Дата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);
// Инициализируем макет и выводим его на экран
Схема = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
КМ = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КМ.Выполнить(Схема, КомпоновщикНастроек.Настройки);
ПК = Новый ПроцессорКомпоновкиДанных;
ПК.Инициализировать(Макет, НаборыДанных);
ПВ = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПВ.УстановитьДокумент(ДокументРезультат);
ПВ.Вывести(ПК, Истина);
---
Если по каким-то причинам вы не хотите настраивать связи внутри СКД (например, если требуется сложная программная обработка строк перед выводом или вы хотите использовать ровно один набор данных в схеме), таблицы можно объединить в коде 1С с помощью индексирования по ключевому полю.
Рассмотрим подробнее эффективный алгоритм слияния двух таблиц значений без использования тяжелых вложенных циклов:
// 1. Создаем результирующую таблицу на основе первой
ТЗ_Итог = ТЗ1.Скопировать();
// 2. Добавляем в нее колонку для хранения даты последнего документа
ТЗ_Итог.Колонки.Добавить("ДатаПоследнегоДокумента", Новый ОписаниеТипов("Дата"));
// 3. Индексируем вторую таблицу по ключевому полю для максимально быстрого поиска
ТЗ2.Индексы.Добавить("Наименование2");
// 4. Бежим по строкам и сопоставляем данные
Для Каждого СтрокаТЗ Из ТЗ_Итог Цикл
// Подготавливаем структуру отбора по ключевому полю
ПараметрыОтбора = Новый Структура("Наименование2", СтрокаТЗ.Наименование);
НайденныеСтроки = ТЗ2.НайтиСтроки(ПараметрыОтбора);
// Если соответствие найдено, переносим данные во вновь созданную колонку
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаТЗ.ДатаПоследнегоДокумента = НайденныеСтроки[0].Дата2;
КонецЕсли;
КонецЦикла;
// 5. Передаем единую объединенную таблицу в СКД
НаборыДанных = Новый Структура("ВходящиеДанные", ТЗ_Итог);
При использовании этого подхода в схеме СКД вам потребуется описать только один набор данных ВходящиеДанные, содержащий поля Наименование, Дата, ПройденноеРасстояние и ДатаПоследнегоДокумента. Этот метод работает быстро даже на больших объемах данных благодаря индексации таблицы приемника.
---
При обмене данными между базами через COM-объекты полезно придерживаться следующих правил:
Неопределено. Это позволит вовремя освободить лицензии 1С и оперативную память на сервере.ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр поддерживается платформой для совместимости, но для больших объемов данных рекомендуется использовать сериализацию через СериализаторXDTO или XML-строки. Это убережет от ошибок несовместимости типов в разных версиях платформы 1С.