Как рассчитать ресурсы в СКД при отборе в группировке, чтобы скрытые строки не попадали в итоги?

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

Разберем одну из классических и наиболее коварных проблем, с которой сталкиваются разработчики при проектировании сложных отчетов в Системе компоновки данных (СКД) 1С. Суть проблемы заключается в следующем: когда мы накладываем отбор на конкретную группировку (например, по определенному бизнес-событию или аналитике), строки в отчете визуально скрываются, но при этом общие итоги и ресурсы вышестоящих группировок продолжают рассчитываться по всем исходным данным, включая скрытые. Получается неприятная ситуация: отчет выглядит отфильтрованным, но итоговые суммы "не бьются" с детальными записями.

В этой статье мы подробно проанализируем, почему так происходит, и рассмотрим три надежных способа решения этой проблемы: средствами функций ресурсов СКД, через вычисляемые поля и путем оптимизации исходного запроса.

Выясняем причину: почему СКД считает скрытые строки?

Для начала разберем внутренний механизм работы компоновщика макета СКД. Процесс компоновки и вывода данных происходит в строго определенном порядке:

  1. Получение данных: Выполняется запрос к базе данных, и СКД получает сырую выборку.
  2. Применение общих отборов: К полученным строкам применяются глобальные отборы отчета (настроенные на вкладке "Настройки" для всего отчета).
  3. Расчет ресурсов: На основе отфильтрованной глобальными отборами выборки СКД рассчитывает агрегатные функции (Сумма, Количество, Максимум и т.д.) для общих итогов и всех уровней группировок.
  4. Построение дерева группировок и применение локальных отборов: СКД начинает выводить группировки. Именно на этом этапе применяется локальный отбор, заданный для конкретной группировки. Подходящие под условие ветки дерева выводятся, а неподходящие — просто скрываются из финального табличного документа.

Поскольку расчет ресурсов (Шаг 3) происходит до фильтрации на уровне конкретных группировок (Шаг 4), скрытые локальным отбором строки все равно остаются в составе рассчитанных итогов. Чтобы обойти это поведение платформы, нам нужно явно указать СКД, как именно собирать итоги.

Способ 1. Использование функции ВычислитьВыражениеСГруппировкойМассив в ресурсах

Это наиболее элегантный и гибкий способ решения проблемы непосредственно на уровне настроек схемы компоновки данных. Мы можем переопределить формулу расчета ресурса для верхних уровней группировок и общих итогов, заставив СКД агрегировать только те данные, которые соответствуют условию нашего отбора.

Предположим, у нас есть группировка Поле5 (например, Бизнес-событие или статья), внутри которой лежат документы детальных записей. Нам нужно суммировать Поле3 (Расход) только по тем группам Поле5, где хотя бы раз в детальных записях был зафиксирован доход (то есть Поле2 > 0).

Перейдем на вкладку "Ресурсы" нашей схемы компоновки данных и настроим расчет для ресурса следующим образом:

  1. Для детальных записей и самой группировки Поле5 оставляем стандартное выражение расчета:
    Выражение: Сумма(Поле3)
    Рассчитывать по: Поле5, Детальные записи
  2. Для вышестоящих группировок (например, по контрагенту или организации Поле1) и для Общего итога мы пропишем специальную формулу:
    Выражение:
    
    Сумма(ВычислитьВыражениеСГруппировкойМассив("Сумма(Поле3)", "Поле5", , "Максимум(Поле2) > 0"))
    

    Рассчитывать по: Поле1, ОбщийИтог

Разберем детально, как работает эта формула:

Функция ВычислитьВыражениеСГруппировкойМассив на лету создает массив значений. Первым параметром мы передаем выражение, которое нужно рассчитать (сумму расходов). Вторым параметром указываем группировку, в разрезе которой нужно выполнить вычисление (наше Поле5). Четвертый параметр — это условие фильтрации. Мы говорим системе: "Выбери только те группы Поле5, для которых максимальное значение дохода Поле2 больше нуля". Полученный массив отфильтрованных сумм передается во внешнюю функцию Сумма(), которая складывает элементы массива и возвращает корректный общий итог.

Способ 2. Реализация через вычисляемые поля СКД

Если вам кажется сложным использование тяжелых функций расчета ресурсов, или если логика отбора завязана на более простые условия, можно пойти по пути создания вспомогательного вычисляемого поля.

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

Разберем этот вариант по шагам:

  1. Перейдем на вкладку "Вычисляемые поля" и добавим новое поле. Назовем его, например, ПризнакНаличияДохода.
  2. В колонке "Выражение" для этого поля укажем вычисление максимума дохода по нашей ключевой группировке Поле5:
    ВычислитьВыражение("Максимум(Поле2)", "Поле5")
  3. Теперь перейдем на вкладку "Ресурсы". Вместо прямого суммирования поля расхода Поле3 мы напишем условие с использованием нашего нового вычисляемого поля:
    Выражение: Сумма(Выбор Когда ПризнакНаличияДохода > 0 Тогда Поле3 Иначе 0 Конец)
    Рассчитывать по: Поле1, ОбщийИтог

При таком подходе СКД сама выполнит логическое ветвление при расчете итогов. Для тех строк группировок, которые не содержат дохода, в ресурс пойдет значение 0, и общий итог сойдется с тем, что видит пользователь на экране.

Способ 3. Перенос логики фильтрации в запрос (Рекомендуемый метод)

Несмотря на то, что СКД предоставляет богатый инструментарий для расчетов "на лету", сложные вычисления функций ресурсов выполняются на сервере приложений 1С в оперативной памяти. Если ваш отчет обрабатывает десятки и сотни тысяч строк, использование функций вроде ВычислитьВыражениеСГруппировкойМассив может привести к значительному снижению производительности и повышенному потреблению памяти.

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

Мы можем заранее определить в запросе, какие группировки содержат доход, и пометить все связанные с ними строки специальным флагом. Посмотрим на пример построения такого запроса с использованием временных таблиц:


// ВЫБИРАЕМ ВСЕ ИСХОДНЫЕ ДАННЫЕ ВО ВРЕМЕННУЮ ТАБЛИЦУ
ВЫБРАТЬ
    ИсходныеДанные.Поле1 КАК Поле1,
    ИсходныеДанные.Поле2 КАК Поле2, // Доход
    ИсходныеДанные.Поле3 КАК Поле3, // Расход
    ИсходныеДанные.Поле5 КАК Поле5, // БС
    ИсходныеДанные.Поле6 КАК Поле6  // Документ
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
    &ТаблицаИсточника КАК ИсходныеДанные
;

////////////////////////////////////////////////////////////////////////////////
// ОПРЕДЕЛЯЕМ ГРУППЫ (БС), ГДЕ БЫЛ ДОХОД
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВТ_ИсходныеДанные.Поле5 КАК Поле5,
    ИСТИНА КАК БС_ИмеетДоход
ПОМЕСТИТЬ ВТ_ГруппыСДоходом
ИЗ
    ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
ГДЕ
    ВТ_ИсходныеДанные.Поле2 > 0
;

////////////////////////////////////////////////////////////////////////////////
// СОЕДИНЯЕМ ИСХОДНЫЕ ДАННЫЕ С ПОЛУЧЕННЫМ ПРИЗНАКОМ
ВЫБРАТЬ
    Данные.Поле1 КАК Поле1,
    Данные.Поле2 КАК Поле2,
    Данные.Поле3 КАК Поле3,
    Данные.Поле5 КАК Поле5,
    Данные.Поле6 КАК Поле6,
    ЕСТЬNULL(Группы.БС_ИмеетДоход, ЛОЖЬ) КАК Поле4 // Наш признак-флаг
ИЗ
    ВТ_ИсходныеДанные КАК Данные
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ГруппыСДоходом КАК Группы
    ПО Данные.Поле5 = Группы.Поле5

Преимущества данного подхода:

← На главную