При построении сложных отчетов и расчетов в запросах 1С разработчики часто сталкиваются с ошибкой деления на ноль. Данная проблема может возникать не только из-за явного наличия нулевых значений в делителях, но и по причине особенностей работы оптимизатора СУБД, наличия значений NULL или громоздких каскадных конструкций ВЫБОР КОГДА.
Разберем подробнее причины возникновения этой ошибки и проанализируем практические способы её устранения.
Проанализируем ситуацию, когда в запросе написана проверка на равенство нуля всех участников вычисления, но платформа все равно выдает ошибку. Это может происходить по следующим причинам:
A / (B / C) / (D / E)), вероятность получения нуля или потери точности в промежуточных вычислениях резко возрастает. Если промежуточный делитель из-за ограниченной разрядности округляется до 0, происходит ошибка.ВЫБРАТЬ могут физически выполняться раньше, чем фильтрация строк в блоке ГДЕ или проверка условий в ВЫБОР.NULL, математические операции с ним могут приводить к неожиданным результатам при отсутствии явной обработки с помощью функции ЕСТЬNULL().Рассмотрим математическое преобразование выражения. Предположим, у нас есть исходное выражение с каскадом делений:
ДнейПросрочки / (СуммаОстаток / СуммаОстаток2) / (СуммаОстаток / СуммаОстаток1)
Вспомним правила алгебры: деление на дробь равносильно умножению на перевернутую дробь. Преобразуем выражение последовательно:
A / (B / C) = (A * C) / B((A * C) / B) / (B / D) = (A * C * D) / (B * B)В итоге вместо четырех операций деления мы получаем всего одну операцию деления! Знаменателем формулы становится квадрат единственной переменной СуммаОстаток * СуммаОстаток, и нам достаточно проверить на ноль всего один показатель вместо трех.
Посмотрим на пример упрощенного запроса:
ВЫБОР
КОГДА ВложенныйЗапрос.СуммаВзаиморасчетовОстаток = 0
ТОГДА 0
ИНАЧЕ ВложенныйЗапрос.ДнейПросрочкиВсего * ВложенныйЗапрос2.СуммаВзаиморасчетовОстаток * ВложенныйЗапрос1.СуммаВзаиморасчетовОстаток / (ВложенныйЗапрос.СуммаВзаиморасчетовОстаток * ВложенныйЗапрос.СуммаВзаиморасчетовОстаток)
КОНЕЦ КАК ПрКонтрактная
Вместо создания глубоких вложенных инструкций ВЫБОР КОГДА ... ТОГДА ВЫБОР КОГДА ... следует объединять все проверки делителей в одну ветку с помощью оператора ИЛИ. Это делает код читаемым и снижает вероятность ошибки.
Разберем пример реализации такого подхода:
ВЫБОР
КОГДА ВложенныйЗапрос.СуммаВзаиморасчетовОстаток = 0
ИЛИ ВложенныйЗапрос2.СуммаВзаиморасчетовОстаток = 0
ИЛИ ВложенныйЗапрос1.СуммаВзаиморасчетовОстаток = 0
ТОГДА 0
ИНАЧЕ ВложенныйЗапрос.ДнейПросрочкиВсего / (ВложенныйЗапрос.СуммаВзаиморасчетовОстаток / ВложенныйЗапрос2.СуммаВзаиморасчетовОстаток) / (ВложенныйЗапрос.СуммаВзаиморасчетовОстаток / ВложенныйЗапрос1.СуммаВзаиморасчетовОстаток)
КОНЕЦ КАК ПрКонтрактная
Если в исходных таблицах возможны незаполненные значения (например, при использовании ЛЕВОЕ СОЕДИНЕНИЕ), их необходимо обернуть функцией ЕСТЬNULL(), чтобы избежать передачи NULL в арифметические операции.
Проанализируем пример кода с проверкой на NULL:
ВЫБОР
КОГДА ЕСТЬNULL(ВложенныйЗапрос1.СуммаВзаиморасчетовОстаток * ВложенныйЗапрос.СуммаВзаиморасчетовОстаток, 0) = 0
ТОГДА 0
ИНАЧЕ ВложенныйЗапрос.ДнейПросрочкиВсего * ВложенныйЗапрос2.СуммаВзаиморасчетовОстаток * ВложенныйЗапрос1.СуммаВзаиморасчетовОстаток / (ВложенныйЗапрос.СуммаВзаиморасчетовОстаток * ВложенныйЗапрос.СуммаВзаиморасчетовОстаток)
КОНЕЦ КАК ПрКонтрактная
Если СУБД всё равно оптимизирует порядок вычислений и вызывает ошибку деления до фильтрации данных, рекомендуется разбить запрос на пакет из двух частей:
ПОМЕСТИТЬ ВТ_ПодготовленныеДанные.Материализация промежуточных данных во временной таблице гарантирует, что оптимизатор SQL-сервера не станет выполнять деление для строк с нулевым знаменателем.