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