При разработке отчетов в 1С часто возникает задача консолидации данных, когда часть информации необходимо получать из внешней информационной базы (например, из базы «Управление автотранспортом» в базу «Бухгалтерия»). Для этого традиционно используется механизм COM-соединения. Однако при реализации таких интеграционных отчетов разработчики часто сталкиваются с тем, что стандартные настройки периода в отчете никак не влияют на получаемые данные, либо отчет начинает работать крайне медленно.
В этой статье мы подробно разберем, почему возникает проблема игнорирования периода, как передать параметры даты из формы отчета в запрос, а также проанализируем критические архитектурные ошибки исходного кода и напишем оптимальный, производительный алгоритм получения данных.
Давайте проанализируем исходный алгоритм, который использовал автор темы. Из внешней базы через COM-соединение получались две таблицы значений (ТЗ). Затем они загружались в локальный запрос текущей базы, соединялись по наименованию водителя, и результат выводился в отчет СКД.
Основная проблема заключалась в том, что в запросах, отправляемых во внешнюю базу, полностью отсутствовало условие фильтрации по дате. Запросы выбирали абсолютно все путевые листы за всю историю ведения учета во внешней базе. Даже когда пользователь менял период на форме отчета, локальный запрос все равно оперировал полным массивом данных, а СКД не могла автоматически наложить фильтр, так как связь между периодом компоновщика и полученной таблицей значений не была настроена должным образом.
Если код формирования отчета написан непосредственно в модуле обычной формы, самым простым (но, как мы увидим далее, не самым оптимальным) решением является прямое обращение к реквизитам формы НачалоПериода и КонецПериода.
Рассмотрим, как передать эти параметры в локальный запрос, который объединяет таблицы значений:
// Предположим, что на форме отчета есть реквизиты НачалоПериода и КонецПериода
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Дата КАК Дата,
| ТЗ.Наименование КАК Наименование,
| ТЗ.ПройденноеРасстояние КАК ПройденноеРасстояние,
| ТЗ2.ОтработаноДней КАК ОтработаноДней,
| ТЗ.ДатаПоследнегоПутевогоЛистаНаМоментНачисления КАК ДатаПоследнегоПутевогоЛиста
|ПОМЕСТИТЬ ВременнаяТаблица
|ИЗ
| &ТЗ КАК ТЗ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ &ТЗ2 КАК ТЗ2
| ПО ТЗ.Наименование = ТЗ2.Наименование
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВременнаяТаблица.Дата,
| ВременнаяТаблица.Наименование,
| ВременнаяТаблица.ПройденноеРасстояние,
| ВременнаяТаблица.ОтработаноДней,
| ВременнаяТаблица.ДатаПоследнегоПутевогоЛиста
|ИЗ
| ВременнаяТаблица КАК ВременнаяТаблица
|ГДЕ
| ВременнаяТаблица.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
// Устанавливаем параметры из реквизитов формы
Запрос.УстановитьПараметр("ТЗ", TabVneshnyaya);
Запрос.УстановитьПараметр("ТЗ2", TabVneshnyaya2);
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Результат = Запрос.Выполнить().Выгрузить();
Этот способ решает проблему «в лоб»: данные на форме теперь будут отфильтрованы по выбранному периоду. Однако у этого подхода есть огромный недостаток — катастрофическое падение производительности при росте базы данных.
Давайте разберем по шагам, что происходит, когда мы накладываем фильтр по периоду только в локальном запросе:
Правило хорошего тона при работе с COM: фильтрацию данных необходимо выполнять на стороне источника! Мы должны запрашивать из внешней базы только те данные, которые заведомо попадают в выбранный период.
Давайте полностью перепишем исходный код автора, исправив попутно все сопутствующие архитектурные ошибки. Мы внедрим пять важных улучшений:
v82.Application на быстрый и легковесный V83.COMConnector (или V82.COMConnector в зависимости от используемой версии платформы). Он не запускает графическую оболочку 1С, работает в разы быстрее и расходует минимум памяти.НачалоПериода и КонецПериода непосредственно во внешний запрос COM-соединения.уатПутевойЛист, а затем соединять их локально.уатПутевойЛист.Ссылка) полностью уничтожала агрегацию по водителю. Мы сгруппируем данные правильно.ЗначениеВСтрокуВнутр / ЗначениеИзСтрокиВнутр, так как этот механизм неофициальный и может аварийно завершить работу платформы при несовпадении релизов баз. Вместо этого мы выгрузим данные в простой массив структур, который отлично передается через COM-границу.Посмотрим на пример реализации оптимизированного кода в модуле формы отчета:
&НаКлиенте
Процедура СформироватьОтчет(Команда)
// Проверяем заполнение обязательных реквизитов периода на форме
Если Не ЗначениеЗаполнено(НачалоПериода) Или Не ЗначениеЗаполнено(КонецПериода) Тогда
ВызватьИсключение "Необходимо заполнить период отчета!";
КонецЕсли;
ДанныеДляОтчета = ПолучитьДанныеИзВнешнейБазы(НачалоПериода, КонецПериода);
// Дальнейший вывод данных в ТабличныйДокумент или передача в СКД...
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДанныеИзВнешнейБазы(Знач НачалоПериода, Знач КонецПериода)
// Инициализируем легкий COM-соединитель вместо Application
Попытка
COMСоединитель = Новый COMОбъект("V83.COMConnector");
Исключение
ВызватьИсключение "Не удалось создать объект V83.COMConnector. Проверьте регистрацию comcntr.dll!";
КонецПопытки;
// Строка подключения к базе-источнику (файловый или серверный вариант)
СтрокаПодключения = "File=""C:\DB\UAT""; Usr=""Администратор""; Pwd=""password"";";
Попытка
ВнешнееСоединение = COMСоединитель.Connect(СтрокаПодключения);
Исключение
ВызватьИсключение "Ошибка подключения к внешней базе: " + ОписаниеОшибки();
КонецПопытки;
// Создаем запрос внутри внешней базы
ВнешнийЗапрос = ВнешнееСоединение.NewObject("Запрос");
ВнешнийЗапрос.Текст =
"ВЫБРАТЬ
| уатПутевойЛист.Водитель1.Наименование КАК ВодительНаименование,
| СУММА(уатПутевойЛист.СпидометрВозвращения - уатПутевойЛист.СпидометрВыезда) КАК ПройденноеРасстояние,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ НАЧАЛОПЕРИОДА(уатПутевойЛист.Дата, ДЕНЬ)) КАК ОтработаноДней,
| МАКСИМУМ(уатПутевойЛист.Дата) КАК ДатаПоследнегоПутевогоЛиста
|ИЗ
| Документ.уатПутевойЛист КАК уатПутевойЛист
|ГДЕ
| уатПутевойЛист.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| уатПутевойЛист.Водитель1.Наименование";
// Передаем параметры периода во внешнюю базу. Платформа автоматически конвертирует типы дат.
// Границы периода приводим к началу и концу дня для корректности выборки.
ВнешнийЗапрос.УстановитьПараметр("НачалоПериода", НачалоДня(НачалоПериода));
ВнешнийЗапрос.УстановитьПараметр("КонецПериода", КонецДня(КонецПериода));
Выборка = ВнешнийЗапрос.Выполнить().Выбрать();
// Создаем локальную таблицу значений для приемки результатов
ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("Водитель", Новый ОписаниеТипов("Строка"));
ТаблицаРезультат.Колонки.Добавить("ПройденноеРасстояние", Новый ОписаниеТипов("Число"));
ТаблицаРезультат.Колонки.Добавить("ОтработаноДней", Новый ОписаниеТипов("Число"));
ТаблицаРезультат.Колонки.Добавить("ДатаПоследнегоПутевогоЛиста", Новый ОписаниеТипов("Дата"));
// Обходим выборку COM-объекта и переносим данные в локальную ТЗ
Пока Выборка.Следующий() Цикл
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.Водитель = Выборка.ВодительНаименование;
НоваяСтрока.ПройденноеРасстояние = Выборка.ПройденноеРасстояние;
НоваяСтрока.ОтработаноДней = Выборка.ОтработаноДней;
НоваяСтрока.ДатаПоследнегоПутевогоЛиста = Выборка.ДатаПоследнегоПутевогоЛиста;
КонецЦикла;
Возврат ТаблицаРезультат;
КонецФункции
Проанализируем преимущества данного кода. Мы избавились от промежуточных сохранений в строки, выполнили всю тяжелую работу по расчету отработанных дней и расстояния на стороне СУБД внешней базы за счет правильной группировки, а сеть нагрузили передачей только итоговых свернутых строк.
Если отчет планируется использовать не только интерактивно на одной конкретной форме, но и выводить на панели отчетов, рассылать по электронной почте или запускать в фоновом режиме по расписанию, жесткая привязка к реквизитам формы недопустима. В СКД получение параметров периода должно быть реализовано в процедуре модуля объекта отчета.
Рассмотрим, как правильно перехватить параметры СКД НачалоПериода и КонецПериода, запустить наш COM-запрос и подставить результат в качестве внешнего набора данных:
ВходящиеДанные. Добавим в него поля: Водитель (Строка), ПройденноеРасстояние (Число), ОтработаноДней (Число), ДатаПоследнегоПутевогоЛиста (Дата).НачалоПериода и КонецПериода с типом Дата.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Получаем настройки пользователя
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// 2. Извлекаем значения параметров периода из СКД
ПараметрНачало = Настройки.ПараметрыДанных.Найти("НачалоПериода");
ПараметрКонец = Настройки.ПараметрыДанных.Найти("КонецПериода");
ЗначениеНачало = ?(ПараметрНачало <> Неопределено И ПараметрНачало.Использование, ПараметрНачало.Значение, НачалоДня(ТекущаяДата()));
ЗначениеКонец = ?(ПараметрКонец <> Неопределено И ПараметрКонец.Использование, ПараметрКонец.Значение, КонецДня(ТекущаяДата()));
// 3. Получаем таблицу данных через COM-соединение, используя наш оптимизированный метод
ТаблицаДанных = ПолучитьДанныеИзВнешнейБазы(ЗначениеНачало, ЗначениеКонец);
// 4. Передаем полученную таблицу в качестве внешнего набора данных СКД
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ВходящиеДанные", ТаблицаДанных);
// 5. Выполняем стандартную компоновку макета
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Благодаря интеграции через модуль объекта, наш отчет стал полностью автономным. Пользователь может настраивать его структуру, группировки, использовать стандартный выбор периодов (например, относительные даты «Вчера», «Этот месяц», «Предыдущий квартал»), а платформа 1С автоматически передаст рассчитанные даты в процедуру выгрузки данных через COM.