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