Разработка и Конфигурация · 9 мин
Как выбрать записи, которых нет в другой таблице 1С
Как в запросе 1С выбрать записи, которых нет в другой таблице: левое соединение с NULL, конструкция НЕ В, сравнение способов, примеры кода и типичные ошибки.
Задача найти записи, которых нет в другой таблице, возникает в 1С постоянно: товары без движений, контрагенты без документов, сотрудники без начислений, номенклатура без цен. Решается она двумя основными способами — через левое соединение с проверкой на NULL и через конструкцию «НЕ В» с вложенным запросом. Оба подхода рабочие, но отличаются по скорости, поведению с NULL-значениями и удобству отладки.
Ниже разобраны оба метода на конкретных примерах встроенного языка запросов 1С, показано, как проверить результат, где чаще всего ошибаются разработчики и какой вариант выбрать в зависимости от объёма данных и структуры таблиц.
Какие есть способы отбора отсутствующих записей
В языке запросов 1С есть два рабочих подхода. Первый — левое соединение: берём основную таблицу, присоединяем к ней вторую по ключевому полю, и там, где пары не нашлось, поля второй таблицы будут равны NULL. Останется отфильтровать именно такие строки условием «ЕСТЬ NULL».
Второй способ — конструкция «НЕ В»: в секции ГДЕ проверяем, что значение поля не входит в результат вложенного запроса. Этот вариант короче записывается, но на больших таблицах обычно работает медленнее, потому что вложенный запрос может выполняться для каждой строки или строить большой временный список значений.
Сравнение вариантов
Быстрее на больших таблицах, предсказуемо работает с NULL, легко отлаживать. Требует чуть больше текста запроса.
Короче и нагляднее на маленьких выборках. Медленнее на больших данных и даёт пустой результат, если во вложенном запросе встречается NULL.
Встречается редко, оправдана только в специфических сценариях с временными таблицами.
Способ 1: левое соединение с проверкой ЕСТЬ NULL
Классическая задача: найти всю номенклатуру, по которой не было продаж за период. Основная таблица — справочник «Номенклатура», вторая — обороты регистра накопления «Продажи». Соединяем по ссылке и отбираем строки, где поле из регистра равно NULL.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи
ПО Номенклатура.Ссылка = Продажи.Номенклатура
ГДЕ
Продажи.Номенклатура ЕСТЬ NULL
Ключевой момент: проверять на NULL нужно поле, которое участвует в связи и гарантированно заполнено во второй таблице. Если проверять необязательное поле, которое само по себе может быть пустым, получите ложные срабатывания — строка есть, а поле пустое, и запись ошибочно попадёт в результат.
Если нужно исключить из выборки помеченные на удаление элементы, добавьте в секцию ГДЕ дополнительное условие на основную таблицу: «НЕ Номенклатура.ПометкаУдаления». Условия на основную таблицу пишутся в ГДЕ, а условия на присоединяемую — лучше в секцию ПО соединения, чтобы не превратить левое соединение во внутреннее.
Способ 2: конструкция НЕ В с вложенным запросом
Тот же результат можно получить короче — через «НЕ В». Во вложенном запросе выбирается список значений ключевого поля из второй таблицы, а внешний запрос отбирает записи, чьё значение в этот список не входит.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.Ссылка В
(ВЫБРАТЬ
Продажи.Номенклатура
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи)
Здесь есть критичный нюанс: если во вложенном запросе встретится значение NULL, вся конструкция «НЕ В» может вернуть пустой результат, потому что сравнение с NULL не даёт ни ИСТИНА, ни ЛОЖЬ. Поэтому при использовании этого способа убедитесь, что поле во вложенном запросе не бывает пустым, либо добавьте туда явный отбор «ГДЕ НЕ Поле ЕСТЬ NULL».
На таблицах с сотнями тысяч записей «НЕ В» заметно проигрывает левому соединению по времени выполнения. Для разовой проверки на небольших данных разница неощутима, для регламентных операций и отчётов предпочтителен первый способ.
Как проверить, что запрос работает правильно
Не доверяйте запросу на глаз — проверьте его на контрольном примере. Возьмите несколько записей, о которых заведомо известно, что пары во второй таблице у них нет, и убедитесь, что они попали в результат. Затем возьмите записи с гарантированной парой и убедитесь, что они отсутствуют.
Полезный приём — временно заменить «ЕСТЬ NULL» на «НЕ ... ЕСТЬ NULL» и посмотреть обратную выборку: записи, у которых пара есть. Сумма обеих выборок должна равняться общему числу записей основной таблицы (с учётом прочих отборов). Если сумма не сходится — в запросе ошибка в условиях соединения.
Как проверить результат
Все заведомо «пустые» записи присутствуют в выборке
Часть записей без пары не попала в результат
Ни одна запись с данными во второй таблице не выбрана
В выборке есть записи, у которых пара существует
Выборка с NULL + выборка без NULL = все записи основной таблицы
Суммы не сходятся — ошибка в условии соединения
Типичные ошибки и как их избежать
Самая частая ошибка — проверка на NULL не того поля. Если проверять необязательный реквизит присоединённой таблицы, запрос отберёт и строки, где пара есть, но реквизит пуст. Всегда проверяйте поле связи.
Вторая ошибка — условия на присоединяемую таблицу в секции ГДЕ. Например, «ГДЕ Продажи.Количество > 0» отсекает все строки с NULL ещё до основной проверки, и левое соединение теряет смысл. Такие условия переносите в секцию ПО.
Третья группа ошибок связана с виртуальными таблицами: забытые параметры периода в оборотах регистра, отсутствие отбора по организации или складу. В результате соединение идёт с избыточным набором данных, запрос работает медленно, а результат может быть неверным из-за лишних записей.
Особые сценарии и уточнения
Отбор по нескольким полям связи
Если пара определяется комбинацией полей — например, номенклатура и склад — перечислите все условия в секции ПО через «И». Проверку на NULL достаточно выполнить по одному из полей связи, но логичнее выбрать то, которое точно заполнено.
Работа через временные таблицы
В сложных запросах удобно сначала поместить данные второй таблицы во временную таблицу с нужными отборами и индексацией, а затем соединять основную таблицу уже с ней. Это ускоряет выполнение и упрощает отладку: содержимое временной таблицы можно посмотреть отдельно.
Сравнение двух списков значений в коде
Иногда обе таблицы уже выгружены в таблицы значений. Тогда отсутствующие записи ищут перебором: для каждой строки первой таблицы вызывают «Найти» по ключевой колонке второй (предварительно создав индекс через «Индексы.Добавить»), и если поиск вернул Неопределено — записи нет. На больших объёмах такой перебор медленнее запроса, но удобен для небольших списков.
- 1Определите основную таблицу и поле (или поля) связи со второй таблицей
- 2Напишите левое соединение с условиями связи в секции ПО
- 3Добавьте в ГДЕ проверку поля связи второй таблицы на ЕСТЬ NULL
- 4Выполните запрос на копии базы и проверьте контрольные записи
- 5Сравните сумму выборок с NULL и без NULL с общим числом записей
- 6При необходимости вынесите вторую таблицу во временную с индексом
Частые вопросы
Что быстрее: левое соединение или НЕ В?
На больших таблицах левое соединение с проверкой ЕСТЬ NULL обычно работает быстрее, потому что СУБД выполняет соединение за один проход. Конструкция «НЕ В» может строить большой список значений и сравнивать его для каждой строки. На малых объёмах разница практически незаметна.
Почему запрос с НЕ В вернул пустой результат?
Почти наверняка во вложенном запросе встретилось значение NULL. Сравнение «значение НЕ В списке с NULL» не даёт ИСТИНА ни для одной строки. Добавьте во вложенный запрос условие «ГДЕ НЕ Поле ЕСТЬ NULL» или перепишите запрос через левое соединение.
Как выбрать записи, которых нет в таблице значений, а не в таблице базы?
Поместите таблицу значений в запрос как параметр, создайте из неё временную таблицу (ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ ИЗ &ТЗ), а затем выполните левое соединение основной таблицы с этой временной таблицей и проверку на NULL.
Как найти записи, которые есть в обеих таблицах?
Это обратная задача: используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ либо то же левое соединение, но с условием «НЕ Поле ЕСТЬ NULL». Внутреннее соединение предпочтительнее — оно короче и быстрее.
Почему после соединения строк стало больше, чем в основной таблице?
Во второй таблице на один ключ приходится несколько записей, и соединение умножает строки. Сгруппируйте вторую таблицу по полю связи перед соединением (например, через вложенный запрос с СГРУППИРОВАТЬ ПО) или используйте виртуальную таблицу с нужной периодичностью.
Можно ли сделать такой отбор в консоли запросов без изменения конфигурации?
Да, оба способа полностью работают в консоли запросов — это стандартный синтаксис языка запросов. Для регламентной проверки безопаснее выполнять запрос на копии базы, особенно если соединяются крупные регистры.
Что важно запомнить
Основной приём — левое соединение с проверкой поля связи на ЕСТЬ NULL в секции ГДЕ. Условия на присоединяемую таблицу держите в секции ПО, конструкцию НЕ В используйте только на малых данных и без NULL во вложенном запросе, а результат всегда проверяйте контрольной суммой обеих выборок.