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