Как правильно рассчитать процент от числа в запросе и СКД 1С?

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

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

Разберем причины такого поведения платформы 1С, выясним математические нюансы работы конструктора запросов и рассмотрим правильные архитектурные способы расчета процентных показателей в СКД.

Причина ошибки: приоритет операций и нормализация запроса конструктором

В языке запросов 1С операции умножения (*) и деления (/) имеют одинаковый приоритет и выполняются строго слева направо. Если требуется вычислить процент от величины, представляющей собой произведение двух полей (например, Количество * Цена), математическая формула выглядит так:

Числитель / (Количество * Цена) * 100

Однако при открытии и последующем закрытии графического конструктора запросов встроенный синтаксический анализатор 1С оптимизирует и нормализует синтаксическое дерево. Он считает внешние скобки в выражении ... / (Поле1 * Поле2) * 100 избыточными и удаляет их. В результате в запросе сохраняется выражение:

Числитель / Поле1 * Поле2 * 100

По правилам СУБД такое выражение вычисляется как ((Числитель / Поле1) * Поле2) * 100. Вместо деления на итоговую сумму числитель делится на количество, а затем умножается на цену и на 100, что и приводит к появлению огромных чисел.

Решение 1: Замена деления на произведение последовательным делением

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

Заменим деление на произведение выражением с последовательными знаками деления:


// Было (скобки удалялись конструктором):
(ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаПродОборот - КассыОбороты.БалансКассыРасход - ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаЗакупОборот) / (ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаПродОборот) * 100 КАК ВПрибыль

// Стало (конструктор не ломает логику):
(ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаПродОборот - КассыОбороты.БалансКассыРасход - ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаЗакупОборот) / ПродажиОбороты.КоличествоОборот / ПродажиОбороты.ЦенаПродОборот * 100 КАК ВПрибыль

Такую запись конструктор запросов не видоизменяет, а порядок вычисления строго слева направо гарантирует математическую корректность операции.

Решение 2: Перенос расчета процентов на вкладку «Ресурсы» СКД (Рекомендуемое)

Расчет относительных величин (процентов, долей, наценок) непосредственно в тексте запроса является частой архитектурной ошибкой при работе с СКД. Дело в том, что проценты нельзя суммировать функцией СУММА() в группировках и общих итогах отчета (сумма процентов по строкам не равна общему проценту по отчету).

Разберем по шагам правильный алгоритм формирования отчета в СКД:

  1. В запросе получаем исключительно абсолютные числовые показатели: общую сумму продаж, сумму закупки и расходы.
  2. На вкладке «Вычисляемые поля» или прямо на вкладке «Ресурсы» создаем расчет абсолютной прибыли: Прибыль = СуммаПродаж - РасходДол - СуммаЗакупки
  3. На вкладке «Ресурсы» схемы компоновки данных задаем расчет процентных показателей с обязательной проверкой деления на ноль:
    
    Выбор 
        Когда Сумма(СумаПродаж) = 0 Тогда 0 
        Иначе (Сумма(Прибыль) / Сумма(СумаПродаж)) * 100 
    Конец
    

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

Решение 3: Использование временных таблиц и разделение запросов

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

Когда в запросе связываются РегистрНакопления.Кассы.Обороты, РегистрНакопления.Продажи.Обороты и Документ.РасходнойДокумент через ЛЕВОЕ СОЕДИНЕНИЕ по неполным ключам (например, только по дате или складу), происходит декартово умножение строк. Строка продаж начинает дублироваться столько раз, сколько документов расхода найдено за этот день.

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

  1. Раздельные наборы данных в СКД: Создаем в схеме компоновки данных два независимых набора данных-запроса (один для касс, второй для продаж) и настраиваем их соединение на вкладке «Связи наборов данных». СКД автоматически выполнит агрегацию данных до соединения таблиц.
  2. Использование временных таблиц (Менеджер временных таблиц): Собираем данные по кассам, продажам и документам в отдельные временные таблицы с группировкой по измерениям, после чего объединяем агрегированные результаты через ОБЪЕДИНИТЬ ВСЕ или связываем по ключевым полям.

Важные правила работы с делением и полями NULL

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

  1. Защита от значений NULL: При использовании ЛЕВОЕ СОЕДИНЕНИЕ отсутствующие данные возвращают значение NULL. Любая операция с NULL (сложение, вычитание, умножение) возвращает NULL. Всегда оборачиваем числовые поля соединений в функцию ЕСТЬNULL(Поле, 0).
  2. Защита от деления на ноль в запросе: Если деление все же выполняется в запросе, используем условную конструкцию:
    
    ВЫБОР 
        КОГДА ПродажиОбороты.КоличествоОборот * ПродажиОбороты.ЦенаПродОборот = 0 ТОГДА 0 
        ИНАЧЕ Числитель / ПродажиОбороты.КоличествоОборот / ПродажиОбороты.ЦенаПродОборот * 100 
    КОНЕЦ
    
← На главную