Почему не работает отбор по полям через Построитель отчета во вложенном запросе

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке отчетов на базе ПостроительОтчета или типового механизма УниверсальныйОтчет в конфигурациях на обычных формах (например, «Управление торговлей 10.3» или «Управление производственным предприятием») разработчики часто сталкиваются с ситуацией, когда пользователь выбирает фильтр на форме, но результат отчета никак не меняется. Особенно часто эта проблема проявляется в сложных составных запросах, где показатели продаж соединяются с остатками, а данные извлекаются через реквизиты документов-регистраторов. Разберем по шагам, почему возникает эта ошибка и как правильно настроить текст запроса, чтобы отборы работали стабильно.

Анализ проблемы: как устроен механизм подстановки условий

Проанализируем типичный сценарий. В отчете требуется получить продажи в разрезе контрагентов или складов, но в регистре накопления ПродажиСебестоимость такие измерения отсутствуют. Чтобы обойти это ограничение, в запрос добавляется обращение к реквизитам документа движения через поле Регистратор. Далее результат помещается во временную таблицу или обрабатывается во вложенном запросе с группировкой, после чего соединяется с остатками товаров.

Когда мы объявляем секции расширения построителя {ГДЕ ...}, механизм анализирует доступные поля запроса и динамически внедряет условия отбора перед выполнением. Если в запросе присутствуют вложенные конструкции, несколько уровней группировок или сложные выражения ВЫБОР КОГДА ... внутри фигурных скобок, механизм сопоставления полей может дать сбой. В результате сгенерированный текст запроса просто не содержит условий, заданных пользователем.

Шаг 1. Проверка реального текста запроса в отладчике

Первое, что необходимо сделать при диагностике «невидимого» отбора, — выяснить, модифицирует ли построитель текст запроса вообще. Посмотрим на практический способ проверки:

  1. Откроем модуль отчета в конфигураторе и найдем процедуру формирования данных (обычно это СформироватьОтчет).
  2. Установим точку останова непосредственно перед вызовом метода Выполнить() у построителя.
  3. Вычислим выражение: ОбщийОтчет.ПостроительОтчета.ПолучитьЗапрос().Текст
  4. Изучим полученный текст: если в блоках ГДЕ отсутствуют параметры выбранного контрагента или склада, значит, построитель не смог связать поле настройки отбора с соответствующим полем запроса.

Шаг 2. Корректная группировка по регистратору во вложенных запросах

Выясним главную причину проблемы, с которой столкнулся автор темы. Во вложенной выборке выполнялась агрегация показателей (например, суммирование полей КоличествоОборот и СтоимостьОборот), а поля периодов группировались через СГРУППИРОВАТЬ ПО. Однако поле Регистратор, на основании которого строились вычисляемые поля контрагента и склада, отсутствовало в секции группировки на промежуточном этапе.

Когда поле отсутствует в группировке подзапроса, СУБД либо выдает синтаксическую ошибку, либо построитель при попытке наложить фильтр на уровень выше просто исключает такое условие. Чтобы исправить поведение, обязательно включим поле Регистратор (или вычисленное на его основе поле) во все промежуточные группировки вложенного запроса:


ВЫБРАТЬ
    ПродажиСебестоимостьОбороты.Номенклатура КАК Номенклатура,
    ПродажиСебестоимостьОбороты.Регистратор КАК Регистратор,
    ПродажиСебестоимостьОбороты.Регистратор.Контрагент КАК Контрагент,
    СУММА(ПродажиСебестоимостьОбороты.КоличествоОборот) КАК КоличествоОборот,
    СУММА(ПродажиСебестоимостьОбороты.СтоимостьОборот) КАК СтоимостьОборот
ИЗ
    РегистрНакопления.ПродажиСебестоимость.Обороты(&ДатаНач, &ДатаКон, Авто, ) КАК ПродажиСебестоимостьОбороты
{ГДЕ
    ПродажиСебестоимостьОбороты.Номенклатура.*,
    ПродажиСебестоимостьОбороты.Регистратор.*,
    ПродажиСебестоимостьОбороты.Регистратор.Контрагент.* КАК Контрагент}
СГРУППИРОВАТЬ ПО
    ПродажиСебестоимостьОбороты.Номенклатура,
    ПродажиСебестоимостьОбороты.Регистратор,
    ПродажиСебестоимостьОбороты.Регистратор.Контрагент

Шаг 3. Упрощение вычисляемых выражений и избавление от лишних конструкций

Посмотрим на конструкцию, часто создающую сложности построителю отчета:


{ГДЕ
    (ВЫБОР
        КОГДА ПродажиСебестоимостьОбороты.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
            ТОГДА ПродажиСебестоимостьОбороты.Регистратор.Контрагент
        ИНАЧЕ ВЫБОР
            КОГДА ПродажиСебестоимостьОбороты.Регистратор ССЫЛКА Документ.КорректировкаРеализации
                ТОГДА ПродажиСебестоимостьОбороты.Регистратор.Контрагент
            КОНЕЦ
    КОНЕЦ).* КАК Контрагент}

Использование сложных условий ВЫБОР внутри фигурных скобок расширения языка запросов для ПостроительОтчета является плохой практикой. Механизм метаданных построителя может некорректно определять составной ссылочный тип такого выражения. Если во всех документах-регистраторах присутствует реквизит с одинаковым наименованием, обратимся к нему напрямую через точку либо используем простое присвоение псевдонима во временной таблице:


ВЫБРАТЬ
    ПродажиСебестоимостьОбороты.Номенклатура КАК Номенклатура,
    ВЫРАЗИТЬ(ПродажиСебестоимостьОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,
    ПродажиСебестоимостьОбороты.КоличествоОборот КАК КоличествоОборот
ПОМЕСТИТЬ ВтПродажи
ИЗ
    РегистрНакопления.ПродажиСебестоимость.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиСебестоимостьОбороты
{ГДЕ
    (ВЫРАЗИТЬ(ПродажиСебестоимостьОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент).* КАК Контрагент}
;

Важный момент оптимизации: поле Регистратор имеет составной ссылочный тип. Прямое обращение Регистратор.Контрагент заставляет СУБД генерировать неявные внешние соединения со всеми существующими таблицами документов-регистраторов, что существенно замедляет выполнение отчета. Конструкция ВЫРАЗИТЬ(... КАК ...) явно указывает системе конкретную таблицу и оптимизирует план запроса.

Шаг 4. Устранение логической ошибки при соединении с остатками

Разберем еще одну распространенную причину, из-за которой кажется, что фильтр «не работает». В итоговом запросе основной таблицей выступает таблица остатков номенклатуры, к которой присоединяются продажи:


ИЗ
    ТаблОстатков КАК ИсточникДанных
    ЛЕВОЕ СОЕДИНЕНИЕ ВложПродажи КАК ВложПродажи
        ПО ИсточникДанных.Номенклатура = ВложПродажи.Номенклатура

Когда пользователь устанавливает отбор по клиенту, продажи этого клиента фильтруются корректно. Однако левое соединение оставляет в результате все строки с остатками, по которым продаж не было (поля продаж принимают значения NULL). В итоге пользователь видит тот же самый длинный список номенклатуры с остатками и считает, что отчет не отреагировал на фильтр.

Чтобы отчет выводил только те позиции, по которым действительно были продажи выбранному клиенту, применим один из следующих подходов:

  1. Заменим ЛЕВОЕ СОЕДИНЕНИЕ на ВНУТРЕННЕЕ СОЕДИНЕНИЕ, если отчет ориентирован исключительно на проданные позиции.
  2. Добавим проверку на заполненность продаж или перенесем отбор на самый верхний уровень запроса:
    
    {ГДЕ
        ВложПродажи.Контрагент.*}
    
    При наложении условия в верхнем блоке ГДЕ строки с NULL в поле контрагента автоматически отсекутся, и в отчет попадут только продажи по выбранному значению.
  3. Обязательно обернем суммовые поля во вложенных запросах функцией ЕСТЬNULL: ЕСТЬNULL(ВложПродажи.КоличествоОборот, 0) КАК КоличествоОборот

Итоги

Для корректной работы отборов в универсальных отчетах на базе построителя придерживаемся следующих правил:

  1. Все поля, по которым строятся вычисляемые реквизиты для фильтрации, обязательно включаем в списки СГРУППИРОВАТЬ ПО вложенных запросов.
  2. Не используем разветвленные конструкции ВЫБОР внутри фигурных скобок {ГДЕ ...}.
  3. Всегда проверяем сгенерированный текст через метод ПостроительОтчета.ПолучитьЗапрос().Текст в точке останова.
  4. Учитываем логику внешних соединений, чтобы строки родительской таблицы не создавали иллюзию неработающего отбора.
← На главную