Как правильно использовать условное суммирование в запросах 1С: избегаем ошибок группировки и повышаем производительность

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

При разработке отчетов и механизмов анализа данных в системе «1С:Предприятие 8» программисты регулярно сталкиваются с необходимостью вычислять условные итоги. Типичный пример — расчет чистой выручки по статье движения денежных средств (ДДС), когда из суммы приходов требуется вычесть сумму редких корректировочных расходов (возвратов), отраженных по этой же статье. Однако неоптимальное проектирование таких запросов может приводить к синтаксическим ошибкам платформы, некорректным результатам или существенному падению производительности СУБД. Проанализируем типичные ошибки и выработаем надежный шаблон решения этой задачи.

Разбор исходного кода и скрытая синтаксическая ошибка группировки

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


ВЫБОР
   КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги        
      ТОГДА ВЫБОР
               КОГДА ДДСОбороты.ПриходРасход = &Приход ТОГДА СУММА(ДДСОбороты.СуммаОборот)
               ИНАЧЕ -СУММА(ДДСОбороты.СуммаОборот)
            КОНЕЦ
   ИНАЧЕ 0
КОНЕЦ КАК ВыручкаОптДеньги

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

Выясним причину этой ошибки. В запросах платформы «1С:Предприятие 8», как и в классическом стандарте SQL, действует строгое правило: все поля выборки, которые не обернуты в агрегатные функции (такие как СУММА, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ), обязаны быть явно указаны в секции группировки. В представленном коде агрегатная функция СУММА находится внутри ветвления ВЫБОР. При этом поля условного выражения — СтатьяДвиженияДенежныхСредств и ПриходРасход — оцениваются СУБД вне агрегатной функции.

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

Правильный шаблон проектирования: СУММА(ВЫБОР КОГДА...)

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


СУММА(ВЫБОР
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги 
          И ДДСОбороты.ПриходРасход = &Приход 
        ТОГДА ДДСОбороты.СуммаОборот
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги 
        ТОГДА -ДДСОбороты.СуммаОборот
    ИНАЧЕ 0
КОНЕЦ) КАК ВыручкаОптДеньги

Разберем по шагам, как этот код обрабатывается на уровне СУБД:

  1. СУБД построчно обходит выборку данных.
  2. Для каждой отдельной строки проверяется логическое условие. Если статья соответствует целевой и это приход — фиксируется исходное значение поля СуммаОборот.
  3. Если это расход по целевой статье — значение СуммаОборот берется с противоположным знаком (инвертируется).
  4. Во всех остальных случаях строка возвращает 0.
  5. Только после этого полученный массив значений (состоящий из положительных сумм, отрицательных сумм и нулей) складывается агрегатной функцией СУММА.

При таком подходе группировать результат по полям СтатьяДвиженияДенежныхСредств и ПриходРасход не требуется, поскольку они полностью инкапсулированы внутри агрегата.

Оптимизация вложенных условий и производительность СУБД

Использование вложенных конструкций ВЫБОР, как в исходном коде, негативно сказывается на читаемости запроса и мешает оптимизатору СУБД построить качественный план выполнения. На больших базах данных это может приводить к нежелательному сканированию таблиц (Table/Index Scan) вместо быстрого поиска по индексам.

Давайте упростим логику условий. Мы можем объединить несколько проверок в одну плоскую цепочку с помощью логических операторов. Также хорошей практикой является отсечение нерелевантных данных в первую очередь:


СУММА(ВЫБОР
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств <> &ВыручкаОптДеньги 
        ТОГДА 0
    КОГДА ДДСОбороты.ПриходРасход = &Приход 
        ТОГДА ДДСОбороты.СуммаОборот
    ИНАЧЕ -ДДСОбороты.СуммаОборот
КОНЕЦ) КАК ВыручкаОптДеньги

В этой конструкции мы сразу возвращаем 0 для всех неподходящих статей движения денежных средств, что снижает нагрузку на процессор при расчете дальнейших условий ветвления.

Логическая ловушка виртуальных таблиц и знаков оборотов

При работе с регистрами накопления в 1С крайне важно учитывать тип регистра (остатки или обороты) и особенности его виртуальных таблиц. Проанализируем две возможные ситуации:

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

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


СУММА(ВЫБОР
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги
        ТОГДА ДДСОбороты.СуммаПриход - ДДСОбороты.СуммаРасход
    ИНАЧЕ 0
КОНЕЦ) КАК ВыручкаОптДеньги

Надежность данных и обязательная обработка NULL

Согласно стандартам разработки фирмы «1С», условное суммирование, особенно если в запросе присутствуют соединения таблиц, крайне рекомендуется оборачивать в функцию контроля пустых значений ЕСТЬNULL.

Если в базе данных за выбранный период не окажется ни одной записи, удовлетворяющей условиям конструкции ВЫБОР, СУБД вернет значение NULL. Это может привести к падению последующего программного кода при попытке сложения чисел или к некрасивому отображению пустых полей в отчетах. Обезопасим наш запрос:


ЕСТЬNULL(СУММА(ВЫБОР
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги 
          И ДДСОбороты.ПриходРасход = &Приход 
        ТОГДА ДДСОбороты.СуммаОборот
    КОГДА ДДСОбороты.СтатьяДвиженияДенежныхСредств = &ВыручкаОптДеньги 
        ТОГДА -ДДСОбороты.СуммаОборот
    ИНАЧЕ 0
КОНЕЦ), 0) КАК ВыручкаОптДеньги

Методологический аспект: стоит ли маскировать ошибки пользователей в коде?

Помимо чисто технической реализации, важно затронуть архитектурный аспект проблемы. Как отмечают опытные специалисты, корректировка расходов по доходным статьям «на лету» в запросах отчетов часто является попыткой скрыть хаос в учете и криворукость конечных пользователей.

С точки зрения методологии и контроля данных такой подход имеет серьезные минусы:

Альтернативное правильное решение: вместо написания сложных «костылей» в запросах отчетов, настройте автоматические контроли проведения документов (например, запретите выбирать статью «Выручка» в документах списания денежных средств). Дополнительно разработайте простой проверочный отчет, который будет моментально выявлять некорректные приходы/расходы по тем статьям, где их логически быть не должно. Это позволит исправить первичные документы и гарантирует прозрачность всей информационной системы.

← На главную