При разработке сложных отчетов на базе Системы компоновки данных (СКД) часто возникает ситуация, когда границы периода для получения остатков или оборотов не являются статичными. Например, нам нужно получить остатки товаров на моменты времени, соответствующие датам конкретных документов (начального и конечного пересчета). Попытка решить эту задачу «в лоб» через соединение временных таблиц или использование условия ГДЕ часто приводит к ошибкам или критическому падению производительности. В этой статье мы разберем, почему стандартные методы запросов не всегда работают в СКД и как правильно передать динамические даты в параметры виртуальных таблиц.
Рассмотрим ситуацию, с которой столкнулся автор темы. В запросе создаются две временные таблицы втНачальныйПересчёт и втКонечныйПересчёт, из которых извлекаются даты. Далее планируется получить данные из регистра накопления СвободныеОстатки.ОстаткиИОбороты. Новичок часто пытается просто соединить эти таблицы или наложить условие:
ГДЕ СвободныеОстаткиИОбороты.Период МЕЖДУ втНачальныйПересчёт.ДатаДокумента И втКонечныйПересчёт.ДатаДокумента
Проанализируем, почему этот путь тупиковый. Во-первых, в виртуальной таблице ОстаткиИОбороты поле Период ведет себя специфически. Если в параметрах таблицы выбрана периодичность Авто, система не может заранее определить состав полей периода, и конструктор запроса может выдавать ошибку об отсутствии поля. Во-вторых, даже если вам удастся обратиться к периоду, использование ГДЕ вместо параметров виртуальной таблицы — это грубая ошибка проектирования. В этом случае СУБД сначала выберет все записи регистра за всю историю, и только потом начнет их фильтровать. Это гарантированный способ «положить» сервер при больших объемах данных.
Этот способ считается наиболее правильным в методологии СКД, так как он не требует написания кода и позволяет использовать встроенные механизмы оптимизации системы. Разберем по шагам, как это реализовать.
Шаг 1: Разделение запросов. Вместо одного огромного пакета запросов в одном наборе данных, мы создадим два разных набора данных типа «Запрос».
ВЫБРАТЬ
ПересчетТоваров.Дата КАК ДатаНачала,
ПересчетТоваровСсылкаВторой.Дата КАК ДатаКонца
ИЗ
Документ.ПересчетТоваров КАК ПересчетТоваров
// ... логика получения второй даты ...
ВЫБРАТЬ
СвободныеОстатки.НачальныйОстаток,
СвободныеОстатки.КонечныйОстаток
ИЗ
РегистрНакопления.СвободныеОстатки.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, , , ) КАК СвободныеОстатки
Шаг 2: Настройка связи. Перейдем на вкладку «Связи наборов данных». Здесь кроется основная магия. Мы указываем НаборДанных1 как источник связи, а НаборДанных2 — как приемник. В колонке «Параметры» для соответствующей связи мы указываем, что поле ДатаНачала из первого набора должно передаваться в параметр НачалоПериода второго набора, а поле ДатаКонца — в параметр КонецПериода.
Проанализируем ситуацию: теперь СКД при выполнении отчета сначала получит даты, а затем для каждой полученной строки дат выполнит запрос к остаткам, подставляя их в параметры виртуальной таблицы. Это обеспечит максимальную производительность на уровне SQL.
Иногда логика получения дат настолько сложна, что описать ее в рамках наборов данных затруднительно. В этом случае мы можем прибегнуть к использованию события ПриКомпоновкеРезультата в модуле объекта отчета. Рассмотрим, как программно перехватить процесс и подставить нужные значения.
Выясним причину, по которой этот метод удобен: он позволяет выполнить произвольный запрос к базе данных перед началом формирования отчета, вычислить любые значения и передать их в параметры СКД. Посмотрим на пример кода:
Процедура ПриКомпоновкеРезультата(ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Отключаем стандартный вывод
// 1. Получаем даты запросом
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Дата ИЗ Документ.ПересчетТоваров ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", КомпоновщикНастроек.Настройки.ПараметрыДанных.Найти("ПересчётНачальный").Значение);
Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
ДатаНачала = Результат.Дата;
КонецЕсли;
// 2. Устанавливаем параметры в компоновщике
ПараметрДатаНачала = КомпоновщикНастроек.Настройки.ПараметрыДанных.Найти("ДатаНачальногоПересчёта");
Если ПараметрДатаНачала <> Неопределено Тогда
ПараметрДатаНачала.Значение = ДатаНачала;
ПараметрДатаНачала.Использование = Истина;
КонецЕсли;
// 3. Продолжаем стандартную компоновку
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Результат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Важно помнить, что при использовании этого метода необходимо следить за тем, чтобы параметры в самой схеме СКД были доступны для редактирования, иначе программная установка может быть проигнорирована при определенных настройках пользователя.
Существует еще один изящный способ, который позволяет остаться в рамках декларативного описания, но добавить динамики. На вкладке «Параметры» схемы компоновки данных для любого параметра можно заполнить колонку «Выражение».
Хотя мы не можем написать там полноценный ВЫБРАТЬ, мы можем вызывать функции общих модулей. Если в вашей конфигурации есть общий модуль с серверной функцией (например, РаботаСДокументами.ПолучитьДату(Ссылка)), вы можете указать в выражении параметра &ДатаНачала следующее:
ОбщийМодульСервер.ПолучитьДатуДокумента(&ПересчётНачальный)
Это позволяет вычислить дату «на лету» в момент компоновки макета. Система сама подставит результат выполнения функции в параметр виртуальной таблицы. Это чистое решение, которое сохраняет логику внутри схемы и не требует переопределения ПриКомпоновкеРезультата.
Разберем подробнее, как СКД обрабатывает параметры виртуальных таблиц. Чтобы система могла гибко управлять периодами, рекомендуется использовать конструкцию с фигурными скобками в тексте запроса:
РегистрНакопления.СвободныеОстатки.ОстаткиИОбороты({&НачалоПериода}, {&КонецПериода}, , , )
Зачем это нужно? Если вы просто укажете &НачалоПериода, то этот параметр станет обязательным. Если же вы заключите его в фигурные скобки {&НачалоПериода}, СКД сможет самостоятельно управлять его использованием. Если в настройках отчета пользователь не укажет период, СКД просто «выбросит» этот фрагмент из итогового запроса к СУБД, что предотвратит ошибки исполнения.
Также обратите внимание на роль поля. На вкладке «Наборы данных» для полей, которые возвращают периоды, должна быть корректно заполнена колонка «Роль». Для поля Период виртуальной таблицы ОстаткиИОбороты важно указать тип «Период» и порядковый номер. Это помогает СКД правильно сопоставлять данные при объединении нескольких наборов.
Проанализировав все способы, можно сделать следующие выводы:
Рассмотренные подходы позволяют создавать гибкие и производительные отчеты, которые корректно работают с динамическими временными интервалами, обеспечивая точные данные на нужные моменты времени.