Как правильно выполнить деление в запросе 1С при объединении таблиц и использовании агрегатных функций?

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

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

Анализ проблемы: почему деление возвращает NULL?

Проанализируем ситуацию, описанную в теме. Разработчик пытается вычислить выполнение плана, деля фактические показатели из одной временной таблицы на плановые показатели из другой. Основная проблема заключается в использовании конструкции ОБЪЕДИНИТЬ ВСЕ с подстановкой NULL в пустые поля.

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

Шаг 1: Правильная подготовка данных в объединениях

Первым делом проанализируем структуру временной таблицы ВТ_Данные. Вместо использования NULL для числовых показателей, которые в дальнейшем будут участвовать в расчетах, необходимо подставлять 0. Это гарантирует, что при суммировании или делении система будет работать с числовым типом данных.

Рассмотрим, как изменить фрагмент кода с объединением:


// Вместо этого:
ВЫБРАТЬ
    ВТ_ПрошлыйГод.ВесПрошлыйГод,
    NULL КАК ВесПлан
ИЗ
    ВТ_ПрошлыйГод КАК ВТ_ПрошлыйГод
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
    NULL,
    ВТ_План.ВесПлан
ИЗ
    ВТ_План КАК ВТ_План

// Нужно писать так:
ВЫБРАТЬ
    ВТ_ПрошлыйГод.ВесПрошлыйГод КАК ВесПрошлыйГод,
    0 КАК ВесПлан
ИЗ
    ВТ_ПрошлыйГод КАК ВТ_ПрошлыйГод
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
    0,
    ВТ_План.ВесПлан
ИЗ
    ВТ_План КАК ВТ_План

Шаг 2: Устранение лишней детализации

Выясним причину, по которой данные не суммируются должным образом. Если в запросе присутствуют поля Регистратор, НомерСтроки или МоментВремени, то каждая запись остается уникальной. Группировка по таким полям не позволяет «схлопнуть» данные по номенклатуре или контрагенту в одну строку.

Проанализируем ситуацию: план обычно вносится на месяц или период, а продажи — конкретными документами. Если мы оставим Регистратор, то в одной строке у нас будет «Продажа» (с весом, но без плана), а в другой — «План» (с планом, но без продаж). Деление в рамках одной строки работать не будет.

Рекомендация: удалите из промежуточных запросов все поля, которые мешают агрегации данных. Оставьте только те измерения, в разрезе которых нужен расчет (например, Номенклатура, Организация, Период в формате начала месяца).

Шаг 3: Сгруппировать до момента деления

Рассмотрим правильную последовательность действий в пакете запросов. Нельзя выполнять деление непосредственно в момент объединения или в выборке, где данные еще не просуммированы по ключевым измерениям.

  1. Сначала собираем все данные в одну временную таблицу через ОБЪЕДИНИТЬ ВСЕ, инициализируя пустые числовые поля нулями.
  2. Затем создаем вторую временную таблицу, в которой выполняем СГРУППИРОВАТЬ ПО основным измерениям, а числовые поля берем с агрегатной функцией СУММА.
  3. И только в финальной выборке из сгруппированной таблицы выполняем арифметические операции.

Пример правильной структуры итогового запроса:


ВЫБРАТЬ
    ВТ_СгруппированныеДанные.Номенклатура,
    СУММА(ВТ_СгруппированныеДанные.Вес12) КАК ВсегоВес,
    СУММА(ВТ_СгруппированныеДанные.ВесПлан) КАК ВсегоПлан
ПОМЕСТИТЬ ВТ_Итоги
ИЗ
    ВТ_СгруппированныеДанные КАК ВТ_СгруппированныеДанные
ГРУППИРОВАТЬ ПО
    ВТ_СгруппированныеДанные.Номенклатура;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Итоги.Номенклатура,
    ВЫБОР 
        КОГДА ВТ_Итоги.ВсегоПлан = 0 ТОГДА 0 
        ИНАЧЕ ВТ_Итоги.ВсегоВес / ВТ_Итоги.ВсегоПлан 
    КОНЕЦ КАК ПроцентВыполнения
ИЗ
    ВТ_Итоги КАК ВТ_Итоги

Шаг 4: Защита от деления на ноль

Посмотрим на пример кода выше. В нем используется конструкция ВЫБОР. Это критически важный момент для стабильной работы системы. В языке запросов 1С нет автоматической обработки деления на ноль — при возникновении такой ситуации запрос завершится аварийно с ошибкой СУБД.

Функция ЕСТЬNULL(Поле, 1), которую часто пытаются применить, спасает только от отсутствия записи, но если в базе данных физически записано значение 0, это приведет к ошибке. Всегда используйте ВЫБОР КОГДА Делитель <> 0.

Шаг 5: Точность вычислений и приведение типов

Проанализируем еще один нюанс — точность. При делении больших чисел результат может быть округлен до целого или до стандартного количества знаков. Чтобы управлять точностью, полезно использовать функцию ВЫРАЗИТЬ.

Например: ВЫРАЗИТЬ(Поле1 / Поле2 КАК ЧИСЛО(15, 3)). Это позволит сохранить три знака после запятой, что часто необходимо для коэффициентов выполнения плана.

Подведем итоги

Для корректного деления в запросе 1С следуйте этим правилам:

Соблюдение этой структуры позволит вам создавать надежные и быстрые отчеты, которые корректно отражают бизнес-показатели без потери данных из-за особенностей обработки NULL в SQL-запросах.

← На главную