В практике разработки на платформе 1С:Предприятие 8 часто возникает задача: получить данные из одной таблицы (например, регистра сведений), а если данных там нет — подставить значение по умолчанию из другой таблицы (например, из справочника). Однако начинающие разработчики часто сталкиваются с «необъяснимым» поведением оператора ВЫБОР при попытке сравнить поле с литералом НЕОПРЕДЕЛЕНО. Проанализируем, почему так происходит, и разберем правильные подходы к решению этой задачи.
Рассмотрим ситуацию, описанную в проблеме. Автор пытается выполнить левое соединение справочника Машины с виртуальной таблицей СрезПоследних регистра сведений. Если записи в регистре нет, он хочет взять дату из справочника.
Вариант 1 (не работает):
ВЫБРАТЬ
Машины.Ссылка КАК Машина,
ВЫБОР
КОГДА СостоянияМашинСрезПоследних.Регистратор = НЕОПРЕДЕЛЕНО
ТОГДА Машины.DateНачалаЭксплуатации
ИНАЧЕ СостоянияМашинСрезПоследних.Регистратор.Дата
КОНЕЦ КАК ДатаД
ИЗ
Справочник.Машины КАК Машины
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияМашин.СрезПоследних(&Дата) КАК СостоянияМашинСрезПоследних
ПО СостоянияМашинСрезПоследних.Машина = Машины.Ссылка
В этом случае, если записи в регистре нет, условие Регистратор = НЕОПРЕДЕЛЕНО не выполняется, и система уходит в ветку ИНАЧЕ, возвращая пустоту.
Вариант 2 (работает, но случайно):
ВЫБОР
КОГДА СостоянияМашинСрезПоследних.Регистратор <> НЕОПРЕДЕЛЕНО
ТОГДА СостоянияМашинСрезПоследних.Регистратор.Дата
ИНАЧЕ Машины.ДатаНачалаЭксплуатации
КОНЕЦ
Этот код отрабатывает «как задумано», но не потому, что условие верно. Выясним причину этого парадокса. Все дело в трехзначной логике SQL и разнице между NULL и НЕОПРЕДЕЛЕНО.
Для программиста 1С важно четко разделять эти понятия, особенно в контексте запросов:
Неопределено. Оно может храниться в базе данных (в полях составного типа) или использоваться во встроенном языке.NULL появляется в запросах в двух основных случаях: при использовании левого или полного соединения (когда в правой таблице нет соответствующей строки) и при агрегатных функциях над пустой выборкой.В регистре сведений поле Регистратор никогда не может содержать значение НЕОПРЕДЕЛЕНО. Оно либо содержит ссылку на документ, либо, если запись при левом соединении не найдена, вся строка таблицы регистра заполняется значениями NULL.
В языке запросов 1С результат логического выражения может быть не только Истина или Ложь, но и Неизвестно (Unknown). Любое сравнение с NULL (будь то =, <>, > или <) всегда возвращает результат Неизвестно.
Проанализируем наши примеры с точки зрения этой логики:
NULL = НЕОПРЕДЕЛЕНО дает Неизвестно. Оператор ВЫБОР трактует это как Ложь и переходит в ветку ИНАЧЕ.NULL <> НЕОПРЕДЕЛЕНО также дает Неизвестно. Система снова игнорирует ветку ТОГДА и переходит в ИНАЧЕ, где как раз и находится нужная автору ДатаНачалаЭксплуатации.Таким образом, второй вариант сработал только потому, что нужный результат был помещен в секцию ИНАЧЕ, а само условие сравнения никогда не было истинным.
Рассмотрим, как правильно написать такой запрос, чтобы он был надежным и понятным.
Для проверки на «пустоту» в результате соединения необходимо использовать специальный оператор ЕСТЬ NULL (IS NULL). Он единственный корректно обрабатывает это значение.
ВЫБОР
КОГДА СостоянияМашинСрезПоследних.Регистратор ЕСТЬ NULL
ТОГДА Машины.ДатаНачалаЭксплуатации
ИНАЧЕ СостоянияМашинСрезПоследних.Регистратор.Дата
КОНЕЦ КАК ДатаД
Это самый лаконичный и производительный способ. Функция ЕСТЬNULL(Поле, ЗначениеЗамены) проверяет первое выражение на NULL и, если это так, возвращает второе выражение.
ВЫБРАТЬ
Машины.Ссылка КАК Машина,
ЕСТЬNULL(СостоянияМашинСрезПоследних.Регистратор.Дата, Машины.ДатаНачалаЭксплуатации) КАК ДатаД
ИЗ
Справочник.Машины КАК Машины
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияМашин.СрезПоследних(&Дата) КАК СостоянияМашинСрезПоследних
ПО СостоянияМашинСрезПоследних.Машина = Машины.Ссылка
Важное замечание: При обращении через точку от NULL (например, Регистратор.Дата, когда Регистратор равен NULL), результатом всего выражения также будет NULL. Поэтому функция ЕСТЬNULL отлично справляется с этой цепочкой.
Часто путаница возникает из-за визуального отображения в консоли запросов. Многие консоли отображают и NULL, и НЕОПРЕДЕЛЕНО, и пустую ссылку одинаково — в виде пустой ячейки или текста «Неопределено». Чтобы точно выяснить, что находится в поле, рекомендуется использовать функции проверки типа в самом запросе:
ВЫБРАТЬ
ТИПЗНАЧЕНИЯ(СостоянияМашинСрезПоследних.Регистратор) КАК ТипПоля
...
Это поможет вам убедиться, что вы работаете именно с NULL, возникшим в результате левого соединения.
Проанализировав ситуацию, мы выяснили:
Во-первых, при левом соединении отсутствие записи всегда дает NULL, а не НЕОПРЕДЕЛЕНО.
Во-вторых, обычные операторы сравнения (=, <>) не работают с NULL так, как мы ожидаем.
В-третьих, для корректной работы всегда следует использовать ЕСТЬ NULL в конструкции ВЫБОР или специализированную функцию ЕСТЬNULL().