Разработчики 1С часто сталкиваются с ситуацией, когда запрос с оператором В работает ожидаемо, а противоположное условие НЕ В (или NOT IN) внезапно возвращает пустой результат, хотя в базе точно есть записи, не входящие в список. Давайте подробно разберем, почему это происходит, как работает троичная логика SQL и какие способы решения этой проблемы существуют.
Рассмотрим типичную ситуацию, описанную в теме. У нас есть справочник сотрудников и временная таблица, в которой собрана часть этих сотрудников. Наша задача — получить всех сотрудников, которых нет в этой временной таблице. Проанализируем пример кода, который приводит к ошибке:
ВЫБРАТЬ
Сотрудники.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ
Справочник.Сотрудники КАК Сотрудники
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
NULL
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Сотрудники.Ссылка КАК Ссылка
ИЗ
Справочник.Сотрудники КАК Сотрудники
ГДЕ
НЕ Сотрудники.Ссылка В
(ВЫБРАТЬ
ВременнаяТаблица.Ссылка
ИЗ
ВременнаяТаблица)
В этом примере во временную таблицу намеренно добавлено значение NULL. В результате основной запрос вернет пустую выборку, даже если в справочнике сотни сотрудников, не попавших в таблицу. Почему так происходит? Ответ кроется в особенностях обработки неопределенных значений в языке запросов.
В обычных языках программирования мы привыкли к бинарной логике: выражение может быть либо Истиной, либо Ложью. Однако в SQL существует третье состояние — «Неизвестно» (UNKNOWN), которое неразрывно связано с типом NULL.
Когда мы используем конструкцию Ссылка НЕ В (Значение1, Значение2, NULL), СУБД разворачивает её в последовательность сравнений через оператор И (AND):
Ссылка <> Значение1 И Ссылка <> Значение2 И Ссылка <> NULL
Проанализируем выполнение этого условия:
NULL (даже самого NULL) всегда возвращает результат UNKNOWN.И, если хотя бы один из операндов равен UNKNOWN, а остальные TRUE, то и весь результат будет UNKNOWN.ГДЕ (WHERE) включает в итоговую выборку только те строки, для которых условие вычисляется как ИСТИНА. Значения ЛОЖЬ и UNKNOWN отсекаются.Таким образом, наличие хотя бы одного NULL в подзапросе при использовании НЕ В «отравляет» весь результат, делая условие невыполнимым для любой строки.
Автор темы заметил, что использование конструкции ВЫБОР решало проблему. Давайте выясним причину:
ГДЕ ВЫБОР
КОГДА АктыБиллинг.Ссылка В (ВЫБРАТЬ АктБиллинг ИЗ АктыБУ) ТОГДА ЛОЖЬ
ИНАЧЕ ИСТИНА
КОНЕЦ
Здесь оператор В (IN) работает по-другому. Если соответствие найдено среди не-NULL значений, возвращается ИСТИНА. Если же соответствия нет, но в списке есть NULL, возвращается UNKNOWN. В конструкции ВЫБОР ветка ИНАЧЕ срабатывает во всех случаях, когда условие КОГДА не является истинным (то есть и для ЛОЖЬ, и для UNKNOWN). Именно поэтому такой обходной путь сработал, но он не является оптимальным.
Самый простой и логичный способ — гарантировать, что в списке для сравнения нет неопределенных значений. Мы можем добавить фильтрацию прямо в подзапрос. Рассмотрим исправленный вариант:
ГДЕ
Сотрудники.Ссылка НЕ В
(ВЫБРАТЬ
ВременнаяТаблица.Ссылка
ИЗ
ВременнаяТаблица
ГДЕ
ВременнаяТаблица.Ссылка ЕСТЬ НЕ NULL)
Это решение эффективно, если вы работаете с небольшими списками или временными таблицами. Однако в 1С есть более профессиональные подходы.
Использование ЛЕВОГО СОЕДИНЕНИЯ с последующей проверкой на NULL — это классический прием в 1С, который зачастую работает быстрее, чем подзапросы с IN. Проанализируем, как переписать наш запрос:
ВЫБРАТЬ
Сотрудники.Ссылка КАК Ссылка
ИЗ
Справочник.Сотрудники КАК Сотрудники
ЛЕВОЕ СОЕДИНЕНИЕ ВременнаяТаблица КАК ВТ
ПО Сотрудники.Ссылка = ВТ.Ссылка
ГДЕ
ВТ.Ссылка ЕСТЬ NULL
Преимущества этого метода:
NULL внутри данных временной таблицы.JOIN, чем для NOT IN.Еще один надежный способ избежать проблем с троичной логикой — использование оператора СУЩЕСТВУЕТ. Этот оператор всегда возвращает только ИСТИНА или ЛОЖЬ, игнорируя неопределенность.
ВЫБРАТЬ
Сотрудники.Ссылка
ИЗ
Справочник.Сотрудники КАК Сотрудники
ГДЕ
НЕ СУЩЕСТВУЕТ
(ВЫБРАТЬ
1
ИЗ
ВременнаяТаблица КАК ВТ
ГДЕ
ВТ.Ссылка = Сотрудники.Ссылка)
В данном случае подзапрос проверяет только сам факт наличия записи. Даже если в ВТ.Ссылка будет NULL, условие связи ВТ.Ссылка = Сотрудники.Ссылка просто не выполнится, и оператор СУЩЕСТВУЕТ вернет ЛОЖЬ, что после инверсии НЕ даст нам нужную ИСТИНУ.
Проанализировав ситуацию, мы можем сформулировать «золотые правила» написания запросов на исключение в 1С:
NULL. Это особенно актуально для внешних источников или таблиц, сформированных сложными объединениями.ЕСТЬ НЕ NULL еще на этапе формирования временной таблицы, чтобы исключить передачу «мусорных» данных в последующие части запроса.Понимание троичной логики SQL позволяет не только исправлять подобные ошибки, но и проектировать более надежные механизмы обработки данных в ваших конфигурациях.