Разработчики в «1С:Предприятии» регулярно сталкиваются со странным поведением запросов: реквизит документа или справочника визуально не заполнен, отладчик при вычислении выражения или просмотре временных таблиц показывает значение NULL, но условие ЕСТЬ NULL упорно не срабатывает. Запрос уходит в ветку выполнения ИНАЧЕ, возвращая некорректные данные или вызывая ошибки выполнения.
В рамках этой статьи мы подробно проанализируем, почему возникает эта иллюзия отладчика, разберем принципиальную разницу между физическим отсутствием данных и пустыми объектными ссылками в базе данных SQL, а также рассмотрим три надежных практических сценария обхода этой проблемы с примерами кода.
Для того чтобы раз и навсегда перестать совершать ошибки при написании условий в запросах, нам необходимо четко разграничить понятия, которыми оперирует платформа «1С:Предприятие» и СУБД на физическом уровне.
NULL появляется в основном в результате левых или полных соединений (когда для строки из левой таблицы не нашлось соответствия в правой таблице), при обращении к несуществующим реквизитам (например, к реквизитам табличной части из шапки), либо при обращении к реквизитам удаленных («битых») ссылок.Документ.Заказ.ПустаяСсылка) — это абсолютно реальное, типизированное значение. На физическом уровне СУБД пустая ссылка представляет собой уникальный идентификатор (UUID), состоящий из одних нулей: 00000000-0000-0000-0000-000000000000. С точки зрения базы данных, это полноценная запись. Поскольку у этого значения есть конкретный тип и заполненный бинарный идентификатор, любая проверка выражения Заказ ЕСТЬ NULL гарантированно вернет ЛОЖЬ.Рассмотрим подробнее ситуацию, с которой столкнулся автор темы на форуме. В запросе было написано следующее условие:
ВЫБОР
КОГДА ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ ЕСТЬ NULL
ТОГДА &МоеПодразделение
ИНАЧЕ ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ.Подразделение
КОНЕЦ КАК Подразделение
Программист видит в отладчике, что результат вычисления равен NULL, но ветка ТОГДА не отрабатывает. Почему так происходит? Разберем этот процесс по шагам:
Когда СУБД выполняет данный запрос, она анализирует поле ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ. В базе данных этот реквизит заполнен пустой ссылкой (нулевым GUID). Условие Заказ ЕСТЬ NULL вычисляется как ЛОЖЬ, так как пустая ссылка — это не NULL.
Запрос переходит к ветке ИНАЧЕ и пытается вычислить выражение обращения через точку: Заказ.Подразделение. Обращение через точку в запросах 1С транслируется в неявное левое соединение (LEFT JOIN) таблицы технологического процесса с таблицей заказов по полю Ссылка.
СУБД выполняет соединение:
LEFT JOIN Документ.Заказ КАК ТаблицаЗаказов
ПО ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ = ТаблицаЗаказов.Ссылка
Так как значением поля является пустая ссылка (нулевой GUID), СУБД ищет в таблице заказов запись, у которой идентификатор равен 00000000-0000-0000-0000-000000000000. Разумеется, такой записи в таблице существующих документов нет. В результате левого соединения все поля из неподтянувшейся таблицы (включая реквизит Подразделение) принимают значение NULL.
Таким образом, отладчик показывает значение NULL не потому, что реквизит Заказ равен NULL, а потому, что результатом всего выражения Заказ.Подразделение стал NULL из-за неявного левого соединения. Это классическая логическая ловушка для разработчиков.
В зависимости от структуры ваших данных и бизнес-логики задачи, выберите один из следующих подходов для корректной обработки незаполненных полей в запросах.
Если реквизит имеет строго определенный (не составной) ссылочный тип, наиболее производительным и лаконичным решением будет явное сравнение значения поля с предопределенной пустой ссылкой этого типа с помощью конструкции ЗНАЧЕНИЕ():
ВЫБОР
КОГДА ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ = ЗНАЧЕНИЕ(Документ.ЗаказПокупателя.ПустаяСсылка)
ТОГДА &МоеПодразделение
ИНАЧЕ ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ.Подразделение
КОНЕЦ КАК Подразделение
Преимущества: Этот способ работает максимально быстро на уровне СУБД, так как не требует выполнения дополнительных левых соединений для проверки условия.
Бывают ситуации, когда реквизит имеет составной тип данных (в котором может быть несколько видов документов или справочников) или когда исходная таблица сама получается в результате внешнего левого соединения (и поле действительно может физически содержать NULL). В таких случаях отлично работает универсальный «трюк» с обращением к системному реквизиту Ссылка:
ВЫБОР
КОГДА ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ.Ссылка ЕСТЬ NULL
ТОГДА &МоеПодразделение
ИНАЧЕ ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ.Подразделение
КОНЕЦ КАК Подразделение
Проанализируем ситуацию и разберем, почему эта конструкция решает сразу все проблемы:
Заказ изначально равен NULL (например, при левом соединении таблиц), то обращение к его реквизиту Ссылка также вернет значение NULL. Условие сработает.Заказ содержит пустую ссылку, СУБД попытается выполнить левое соединение с таблицей объектов для получения поля Ссылка. Так как объект с пустым GUID отсутствует, соединение вернет NULL. Условие снова сработает!Ссылка также вернет NULL, защищая ваш отчет от вывода некорректных пустых строк.Иногда разработчики пытаются решить задачу «в лоб», используя стандартную функцию платформы ЕСТЬNULL() следующим образом: ЕСТЬNULL(ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ, &МоеПодразделение). Этот вариант работать не будет, так как функция проверяет только значение NULL и проигнорирует пустую ссылку.
Однако функцию ЕСТЬNULL() можно элегантно применить к конечному результату неявного соединения:
ЕСТЬNULL(ЗаданиеНаПроизводствоВыпускТехПроцесс.Заказ.Подразделение, &МоеПодразделение) КАК Подразделение
Важное предупреждение: Используйте этот вариант с осторожностью. Если у вас в базе данных есть заполненный (существующий) документ Заказ, но внутри этого документа реквизит Подразделение случайно оказался не заполнен (то есть равен пустой ссылке), этот метод вернет пустую ссылку подразделения, а не ваше значение по умолчанию &МоеПодразделение. Если бизнес-логика требует подставлять значение по умолчанию исключительно при отсутствии самого заказа, всегда используйте конструкцию ВЫБОР КОГДА из Способа 1 или Способа 2.