При разработке отчетов или алгоритмов планирования в 1С программисты часто сталкиваются с ситуацией, когда запрос игнорирует товары, по которым нет остатков. Казалось бы, если остатка нет, то условие КоличествоОстаток <= 0 должно выполняться, но на практике ветка кода в конструкции ВЫБОР просто не срабатывает. Давайте подробно проанализируем, почему это происходит, и разберем правильные методы решения этой задачи.
Главная причина кроется в том, как база данных обрабатывает неопределенные значения (NULL). Проанализируем ситуацию: когда мы выполняем ЛЕВОЕ СОЕДИНЕНИЕ таблицы материалов с виртуальной таблицей остатков, для тех позиций, по которым движений никогда не было, в поле остатка попадает не число 0, а значение NULL.
В языке запросов 1С (как и в стандартном SQL) любое сравнение с NULL (кроме специальных операторов) возвращает результат «Unknown» (неизвестно), который в логических условиях трактуется как Ложь. Рассмотрим пример:
0 <= 0 — Истина (True).-5 <= 0 — Истина (True).NULL <= 0 — Ложь (False).Именно поэтому проверка М.КоличествоОстаток <= 0 не работает для «пустых» позиций. Система видит NULL, понимает, что это не меньше и не равно нулю, и переходит к блоку ИНАЧЕ.
Рассмотрим самый эффективный и часто используемый способ решения — преобразование NULL в числовое значение прямо «на лету». Для этого в 1С предназначена функция ЕСТЬNULL(Поле, ЗначениеЗамены). Проанализируем, как должен выглядеть фрагмент кода из исходного вопроса после исправления:
ВЫБОР
КОГДА ЕСТЬNULL(М.КоличествоОстаток, 0) <= 0
ТОГДА И.КоличествоОстаток * И.МассаЗаготовки
ИНАЧЕ 0
КОНЕЦ КАК ИНадоКол
Теперь, если остатка нет (значение NULL), функция заменит его на реальный ноль, и условие 0 <= 0 отработает корректно. Это универсальный подход при работе с левыми соединениями.
Иногда требуется проверить именно отсутствие записи в присоединяемой таблице. В этом случае нужно использовать оператор ЕСТЬ NULL. Важно помнить, что сравнение через обычное равно (= NULL) является грубой ошибкой и всегда будет возвращать «Ложь».
Разберем правильный синтаксис:
ВЫБОР
КОГДА М.КоличествоОстаток ЕСТЬ NULL
ТОГДА // Логика для отсутствующих остатков
ИНАЧЕ // Логика для существующих остатков
КОНЕЦ
Выясним еще одну важную деталь: виртуальные таблицы остатков в 1С (например, РегистрБухгалтерии.Хозрасчетный.Остатки) физически не хранят строки с нулевыми итогами. Если по счету или номенклатуре остаток стал равен нулю, запись из итогов может быть удалена. Поэтому при соединении таблиц вы всегда будете получать либо число (не равное нулю), либо NULL.
Важный нюанс: Если вы хотите получить «честный» ноль в результате запроса, всегда оборачивайте поля ресурсов виртуальных таблиц в ЕСТЬNULL.
Проанализируем пример запроса, предоставленный автором темы в Сообщении 11. В нем содержится еще одна критическая ошибка, которая мешает выполнению — отсутствие группировки при использовании агрегатных функций.
// ОШИБОЧНЫЙ ФРАГМЕНТ
ВЫБРАТЬ
По.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА И.КоличествоОстаток - СУММА(По.Количество) <= 0
ТОГДА (И.КоличествоОстаток - СУММА(По.Количество)) * По.Номенклатура.МассаЗаготовки
ИНАЧЕ 0
КОНЕЦ КАК РасходМ
...
ИЗ Документ.Текущее.Материалы КАК По
...
В данном случае используется функция СУММА. По правилам 1С, если в списке выбора есть хотя бы один агрегат (Сумма, Количество, Минимум, Максимум), то все остальные поля, не обернутые в агрегаты, обязательно должны быть перечислены в секции СГРУППИРОВАТЬ ПО.
Чтобы исправить запрос, необходимо либо добавить группировку по всем полям номенклатуры и остатков, либо (что более правильно) сначала рассчитать суммы по документам во временной таблице, а уже потом соединять их с остатками.
Рассмотрим ситуацию с типизацией. В запросе автора используется условие соединения: ПО (По.Номенклатура = И.Субконто1). Поле Субконто1 имеет составной тип. Для повышения производительности и исключения ошибок приведения типов, рекомендуем использовать оператор ВЫРАЗИТЬ:
ПО (По.Номенклатура = ВЫРАЗИТЬ(И.Субконто1 КАК Справочник.Номенклатура))
Это подскажет планировщику SQL-запроса, с каким конкретно типом данных ведется работа, и ускорит выполнение операции соединения.
Если условие по-прежнему кажется вам подозрительным, воспользуйтесь следующими приемами:
М.КоличествоОстаток и ТИПЗНАЧЕНИЯ(М.КоличествоОстаток). Это наглядно покажет, где система видит число, а где — NULL.( {Счет В (&Счета)} ), а не в секции ГДЕ. Это критично для производительности и правильной работы ЛЕВОГО СОЕДИНЕНИЯ.Подводя итог: для корректной работы сравнения с нулем при наличии потенциальных пустых записей, всегда используйте ЕСТЬNULL(Поле, 0) <= 0. Это гарантирует, что даже при отсутствии движений по регистру ваша логика отработает предсказуемо.