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