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