Одной из самых распространенных и при этом коварных ошибок при разработке запросов в системе «1С:Предприятие» является деление на ноль. Ситуация осложняется тем, что даже наличие, казалось бы, корректной проверки через конструкцию ВЫБОР не всегда спасает от аварийного завершения запроса. В рамках этой статьи мы подробно разберем, почему возникает эта ошибка, как оптимизатор SQL обрабатывает ваши условия и как правильно составить запрос, чтобы он работал стабильно на любых данных.
Рассмотрим типичную ситуацию. Программист пишет запрос для получения средней цены, где сумма делится на количество. Чтобы избежать ошибки, добавляется условие:
ВЫБОР
КОГДА Хозрасчетный.КоличествоКт = 0
ТОГДА 0
ИНАЧЕ СРЕДНЕЕ(Хозрасчетный.Сумма / Хозрасчетный.КоличествоКт)
КОНЕЦ КАК Цена
На первый взгляд, логика безупречна: если количество равно нулю, мы возвращаем ноль и не выполняем деление. Однако на практике этот запрос часто вылетает с ошибкой «Деление на 0 значения типа Numeric». Проанализируем ситуацию и поймем, в чем подвох.
Основная проблема заключается в том, как работают агрегатные функции (такие как СРЕДНЕЕ, СУММА, МАКСИМУМ) в связке с SQL-оптимизатором. Когда вы помещаете агрегатную функцию внутрь ИНАЧЕ, СУБД (например, MS SQL Server или PostgreSQL) может попытаться вычислить выражение для всего набора данных еще до того, как будет применено условие ВЫБОР. Это связано с планом выполнения запроса: оптимизатор может решить, что сначала нужно рассчитать частное от деления для каждой строки, а уже потом агрегировать и фильтровать результат.
Чтобы гарантированно избежать ошибки, мы должны гарантировать, что деление никогда не произойдет, если делитель равен нулю. Самый надежный способ — поместить конструкцию ВЫБОР внутрь агрегатной функции. Разберем этот пример по шагам:
СРЕДНЕЕ(
ВЫБОР
КОГДА ЕСТЬNULL(Хозрасчетный.КоличествоКт, 0) = 0
ТОГДА 0
ИНАЧЕ Хозрасчетный.Сумма / Хозрасчетный.КоличествоКт
КОНЕЦ
) КАК Цена
В этом случае система сначала для каждой строки проверяет условие на ноль, заменяет результат на 0 (или NULL, если нужно исключить строку из расчета среднего), и только потом вычисляет среднее значение. Такой подход лишает СУБД возможности произвести деление на ноль на этапе предварительных вычислений.
Проанализируем еще один важный аспект. Если в запросе используется ЛЕВОЕ СОЕДИНЕНИЕ, то в правой таблице данных может просто не оказаться. В этом случае поле КоличествоКт примет значение NULL. В языке запросов 1С операция NULL = 0 вернет ЛОЖЬ (точнее, неизвестность), и условие КОГДА КоличествоКт = 0 не сработает. Однако при попытке деления Сумма / NULL результат будет NULL, что обычно не вызывает ошибку, но если платформа попытается привести это к числу в определенных контекстах, могут возникнуть проблемы.
Поэтому всегда используйте функцию ЕСТЬNULL. Посмотрим на правильную проверку:
ВЫБОР
КОГДА ЕСТЬNULL(Хозрасчетный.КоличествоКт, 0) = 0
ТОГДА 0
ИНАЧЕ Хозрасчетный.Сумма / Хозрасчетный.КоличествоКт
КОНЕЦ
Важный нюанс: использование ЕСТЬNULL в секции ГДЕ может существенно замедлить запрос, так как это мешает использованию индексов. Но внутри секции ВЫБРАТЬ это стандартная и безопасная практика.
Рассмотрим ситуацию с точки зрения бухгалтерского и управленческого учета. Использование функции СРЕДНЕЕ(Сумма / Количество) часто является ошибкой в самой логике задачи.
Представьте две закупки:
Если мы вычислим СРЕДНЕЕ(Цена), мы получим (100 + 10) / 2 = 55 руб.
Но реальная средневзвешенная цена составляет: (100 + 1000) / 101 = 10.89 руб.
Поэтому в большинстве реальных задач вместо функции СРЕДНЕЕ следует использовать отношение суммы к количеству по всем строкам. Проанализируем правильный код:
СУММА(Хозрасчетный.Сумма) / ВЫБОР
КОГДА СУММА(Хозрасчетный.КоличествоКт) = 0
ТОГДА 1
ИНАЧЕ СУММА(Хозрасчетный.КоличествоКт)
КОНЕЦ КАК СредневзвешеннаяЦена
Обратите внимание на использование ТОГДА 1 в делителе. Если количество равно нулю, деление на 1 не вызовет ошибки и вернет 0 (так как в числителе тоже будет 0), либо вы можете явно вернуть 0 через ВЫБОР для всего выражения целиком.
Когда мы используем агрегатные функции, важно правильно настроить секцию СГРУППИРОВАТЬ ПО. Если вы рассчитываете цену как ресурс, поле КоличествоКт не должно попадать в группировку, иначе вы получите цену для каждой отдельной строки с уникальным количеством, что лишает смысла использование агрегата СРЕДНЕЕ или СУММА.
Посмотрим на пример правильной структуры запроса:
ВЫБРАТЬ
ХозрасчетныйСубконто.Значение КАК Номенклатура,
СУММА(Хозрасчетный.Сумма) КАК ОбщаяСумма,
СУММА(Хозрасчетный.КоличествоКт) КАК ОбщееКоличество,
СУММА(Хозрасчетный.Сумма) / ВЫБОР
КОГДА СУММА(Хозрасчетный.КоличествоКт) = 0
ТОГДА 1
ИНАЧЕ СУММА(Хозрасчетный.КоличествоКт)
КОНЕЦ КАК Цена
ИЗ
РегистрБухгалтерии.Хозрасчетный.Субконто КАК ХозрасчетныйСубконто
ГДЕ
ХозрасчетныйСубконто.Значение = &Номенклатура
СГРУППИРОВАТЬ ПО
ХозрасчетныйСубконто.Значение
Для исключения ошибки «Деление на 0» придерживайтесь следующих правил:
СРЕДНЕЕ(ВЫБОР КОГДА Кол = 0 ТОГДА 0 ИНАЧЕ Сумма / Кол КОНЕЦ).NULL, который нужно преобразовать в числовой ноль перед сравнением.Соблюдение этих структурных правил позволит вам создавать надежные механизмы получения отчетности, которые не подведут даже при наличии некорректных данных в учетной системе.