Как избежать ошибки «Деление на ноль» при расчете цены в запросах 1С?

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

Возникновение ошибки «Деление на ноль» в запросах 1С, основанных на SQL Server, является частой ситуацией. Эта ошибка происходит, когда знаменатель в математическом выражении деления равен нулю или содержит значение NULL, что является математически неопределенным. В нашей сегодняшней теме мы рассмотрим, как корректно обрабатывать такие ситуации, чтобы запросы выполнялись без ошибок, возвращая приемлемые результаты, например, ноль или NULL, в случае невозможности вычисления.

Давайте проанализируем исходную проблему. Пользователь столкнулся с ошибкой при заполнении документа, которая указывала на деление на ноль в запросе. Текст запроса содержал расчет цены как Товары.СтоимостьОстаток / ЕСТЬNULL(Товары.КоличествоОстаток, 0) КАК Цена. Очевидно, что при КоличествоОстаток, равном нулю или NULL, произойдет ошибка. Наша задача – модифицировать запрос таким образом, чтобы он корректно обрабатывал эти случаи.

Причина возникновения ошибки «Деление на ноль»

Рассмотрим проблемный участок кода, который вызвал ошибку:


ВЫБРАТЬ
    ТоварыВЭксплуатацииОстатки.ФизЛицо,
    Товары.Номенклатура,
    Товары.Организация,
    Товары.СтруктурнаяЕдиница,
    Товары.КоличествоОстаток КАК КоличествоУчет,
    Товары.КоличествоОстаток КАК Количество,
    Товары.СтоимостьОстаток КАК СуммаУчет,
    Товары.СтоимостьОстаток КАК Сумма,
    Товары.СтоимостьОстаток / ЕСТЬNULL(Товары.КоличествоОстаток, 0) КАК Цена
ИЗ
    РегистрНакопления.Товары.Остатки(&Дата, ФизЛицо = &Склад) КАК Товары

Здесь проблема заключается в выражении Товары.СтоимостьОстаток / ЕСТЬNULL(Товары.КоличествоОстаток, 0) КАК Цена. Функция ЕСТЬNULL(Товары.КоличествоОстаток, 0) заменяет значение NULL на 0. Если же само Товары.КоличествоОстаток уже равно нулю, то знаменатель все равно будет равен нулю, что приводит к ошибке деления.

Решение с использованием оператора ВЫБОР КОГДА

Одним из самых надежных и понятных способов избежать деления на ноль является использование оператора ВЫБОР КОГДА (аналог CASE WHEN в SQL). Мы можем явно проверить значение знаменателя перед выполнением операции деления.

Предлагаем следующее решение, которое было предложено на форуме и является наиболее логичным:


ВЫБРАТЬ
    ТоварыВЭксплуатацииОстатки.ФизЛицо,
    Товары.Номенклатура,
    Товары.Организация,
    Товары.СтруктурнаяЕдиница,
    Товары.КоличествоОстаток КАК КоличествоУчет,
    Товары.КоличествоОстаток КАК Количество,
    Товары.СтоимостьОстаток КАК СуммаУчет,
    Товары.СтоимостьОстаток КАК Сумма,
    ВЫБОР
        КОГДА ЕСТЬNULL(Товары.КоличествоОстаток, 0) = 0 ТОГДА 0
        ИНАЧЕ Товары.СтоимостьОстаток / Товары.КоличествоОстаток
    КОНЕЦ КАК Цена
ИЗ
    РегистрНакопления.Товары.Остатки(&Дата, ФизЛицо = &Склад) КАК Товары

В этом решении мы делаем следующее:

  1. Используем ЕСТЬNULL(Товары.КоличествоОстаток, 0), чтобы убедиться, что мы работаем с числовым значением, заменяя NULL на 0.
  2. Проверяем, если результат этой функции равен 0.
  3. Если знаменатель равен 0, то вместо деления мы просто возвращаем 0 в качестве цены. Это позволяет избежать ошибки и дает осмысленный результат (например, товар без количества не имеет определенной цены или его цена принимается равной нулю).
  4. В противном случае, когда количество больше нуля, мы выполняем стандартное деление Товары.СтоимостьОстаток / Товары.КоличествоОстаток.

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

Обработка нулевых значений в других полях

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


ВЫБРАТЬ
    ТоварыВЭксплуатацииОстатки.ФизЛицо,
    Товары.Номенклатура,
    Товары.Организация,
    Товары.СтруктурнаяЕдиница,
    Товары.КоличествоОстаток КАК КоличествоУчет,
    ВЫБОР
        КОГДА ЕСТЬNULL(Товары.КоличествоОстаток, 0) > 0 ТОГДА Товары.КоличествоОстаток
        ИНАЧЕ 0
    КОНЕЦ КАК Количество,
    Товары.СтоимостьОстаток КАК СуммаУчет,
    ВЫБОР
        КОГДА ЕСТЬNULL(Товары.СтоимостьОстаток, 0) > 0 ТОГДА Товары.СтоимостьОстаток
        ИНАЧЕ 0
    КОНЕЦ КАК Сумма,
    ВЫБОР
        КОГДА ЕСТЬNULL(Товары.КоличествоОстаток, 0) = 0 ТОГДА 0
        ИНАЧЕ Товары.СтоимостьОстаток / Товары.КоличествоОстаток
    КОНЕЦ КАК Цена
ИЗ
    РегистрНакопления.Товары.Остатки(&Дата, ФизЛицо = &Склад) КАК Товары

Здесь мы создаем дублирующие поля Количество и Сумма, которые будут содержать 0, если их учетные аналоги равны нулю или NULL, а также при значении меньше нуля. Это может быть полезно для отделения "учетных" данных (которые могут быть отрицательными из-за ошибок учета) от "расчетных" или "физических" значений.

Учетные аспекты и обработка отрицательных значений

На форуме также развернулась дискуссия о том, как следует поступать с отрицательными значениями количества или стоимости. Важно понимать, что «Деление на ноль» — это техническая ошибка, а отрицательные значения — это потенциальные ошибки учета или специфические бизнес-ситуации. Скрытие отрицательных значений, приравнивая их к нулю, может помешать обнаружению и исправлению проблем в учете. Например, если на складе оказалось -10 единиц товара, это должно быть поводом для проверки, а не просто отображаться как 0.

Наш подход с ВЫБОР КОГДА ЕСТЬNULL(Товары.КоличествоОстаток, 0) = 0 ТОГДА 0 ... решает именно проблему деления на ноль. Если КоличествоОстаток будет отрицательным (например, -5), деление СтоимостьОстаток / -5 произойдет без ошибки, и цена будет рассчитана с отрицательным значением. Это может быть предпочтительнее, поскольку такая "странная" цена сигнализирует о проблеме в исходных данных.

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


ВЫБОР
    КОГДА ЕСТЬNULL(Товары.КоличествоОстаток, 0) <= 0 ТОГДА 0 // Учитываем 0 и отрицательные значения
    ИНАЧЕ Товары.СтоимостьОстаток / Товары.КоличествоОстаток
КОНЕЦ КАК Цена

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

Альтернативные методы решения проблемы «Деление на ноль»

Помимо оператора ВЫБОР КОГДА, существуют и другие, более лаконичные методы, которые активно используются в SQL Server и могут быть применены в запросах 1С (хотя их доступность может зависеть от версии платформы 1С и используемого механизма построения запросов). Рассмотрим их подробнее.

1. Функция NULLIF

Функция NULLIF(выражение1, выражение2) возвращает NULL, если выражение1 равно выражение2. В противном случае она возвращает выражение1. Деление на NULL в SQL не вызывает ошибку, а возвращает NULL в качестве результата деления. Это часто является приемлемым поведением для отсутствующей или неопределённой цены.

Пример использования:


ВЫБРАТЬ
    ...,
    Товары.СтоимостьОстаток / NULLIF(Товары.КоличествоОстаток, 0) КАК Цена
ИЗ
    ...

Здесь, если Товары.КоличествоОстаток равно 0, функция NULLIF вернет NULL, и деление на NULL приведет к тому, что Цена также станет NULL.

2. Комбинация NULLIF и COALESCE (или ЕСТЬNULL)

Если вместо NULL в результате деления на ноль нам необходимо получить конкретное значение (например, 0), можно использовать функцию COALESCE (или ее аналог в 1С - ЕСТЬNULL) вместе с NULLIF. Функция COALESCE возвращает первое не-NULL выражение из списка.

Пример использования:


ВЫБРАТЬ
    ...,
    ЕСТЬNULL(Товары.СтоимостьОстаток / NULLIF(Товары.КоличествоОстаток, 0), 0) КАК Цена
ИЗ
    ...

В этом случае, если NULLIF вернет NULL (то есть, КоличествоОстаток было 0), ЕСТЬNULL заменит этот NULL на 0.

3. Фильтрация данных с помощью ГДЕ (WHERE)

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

Пример:


ВЫБРАТЬ
    ...,
    Товары.СтоимостьОстаток / Товары.КоличествоОстаток КАК Цена
ИЗ
    РегистрНакопления.Товары.Остатки(&Дата, ФизЛицо = &Склад) КАК Товары
ГДЕ
    Товары.КоличествоОстаток <> 0

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

4. Настройка SET ARITHABORT OFF (для SQL Server)

В SQL Server существует настройка SET ARITHABORT OFF, которая может изменить поведение при делении на ноль, заставляя его возвращать NULL вместо ошибки. Однако этот подход не рекомендуется использовать как основное решение для предотвращения ошибок деления на ноль в 1С. SET ARITHABORT ON является значением по умолчанию для большинства клиентских приложений, и его изменение может повлиять на планы выполнения запросов и производительность, а также скрыть потенциальные проблемы с данными. Использование этой настройки обычно рассматривается как крайняя мера или для очень специфических сценариев, но не как стандартное решение для запросов 1С.

Резюмируем

Выбор метода для предотвращения ошибки «Деление на ноль» зависит от желаемого поведения: получить NULL, получить 0, или вовсе исключить такие строки из результата.

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

Всегда помните, что корректная обработка деления на ноль не только предотвращает ошибки, но и делает ваши отчеты и расчеты более точными и информативными.

← На главную