Как правильно выбрать данные, которых нет в другой таблице: Левое соединение или оператор В

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие часто возникает классическая задача: необходимо выбрать записи из одной таблицы, которых гарантированно нет в другой. Типичный пример — выбор "не лысых" людей из общего списка, при наличии отдельной таблицы "лысых". Начинающие и опытные разработчики часто спорят, какой метод является более "грамотным", производительным и стабильным. Давайте подробно разберем основные способы реализации этой задачи, проанализируем их плюсы и минусы, а также выясним, что происходит "под капотом" СУБД.

Способ 1: Использование ЛЕВОЕ СОЕДИНЕНИЕ с проверкой на NULL

Этот метод считается стандартным и наиболее рекомендуемым в среде 1С. Суть его заключается в том, что мы соединяем основную таблицу со вспомогательной (черным списком) по ключевому полю. Если соответствие не найдено, поля из правой таблицы заполняются значением NULL. Именно эти строки нам и нужны.

Рассмотрим пример кода:


ВЫБРАТЬ
    ТаблицаВсех.ФизЛицо КАК ФизЛицо
ИЗ
    ТаблицаВсех КАК ТаблицаВсех
ЛЕВОЕ СОЕДИНЕНИЕ
    ТаблицаЛысых КАК ТаблицаЛысых
ПО
    ТаблицаВсех.ФизЛицо = ТаблицаЛысых.ФизЛицо
ГДЕ
    ТаблицаЛысых.ФизЛицо ЕСТЬ NULL

Преимущества этого подхода:

  1. Соответствие стандартам: Фирма 1С рекомендует использовать именно соединения, так как они более наглядно описывают топологию данных для оптимизатора.
  2. Гибкость: Если нам потребуется не только отсечь записи, но и получить какие-то дополнительные данные из второй таблицы (например, дату, когда человек попал в список), нам не придется переписывать весь запрос.
  3. Стабильность: Этот вариант предсказуемо работает на всех СУБД (MS SQL Server, PostgreSQL, Oracle).

Способ 2: Оператор НЕ В (NOT IN) с вложенным запросом

Этот вариант кажется более интуитивно понятным с точки зрения человеческой логики: "выбрать всех, кто НЕ В списке". Однако за этой простотой скрываются серьезные технические риски.

Рассмотрим реализацию:


ВЫБРАТЬ
    ТаблицаВсех.ФизЛицо
ИЗ
    ТаблицаВсех
ГДЕ
    НЕ ТаблицаВсех.ФизЛицо В (ВЫБРАТЬ ТаблицаЛысых.ФизЛицо ИЗ ТаблицаЛысых)

Почему же опытные разработчики называют конструкцию НЕ В "кошмаром"? Разберем причины по шагам:

Проблема NULL в подзапросе: Это самый коварный момент. Согласно логике SQL, если вложенный запрос вернет хотя бы одну строку со значением NULL, то весь оператор НЕ В вернет пустой результат. Ваша выборка будет пустой, даже если в основной таблице тысячи записей. Это происходит из-за трехзначной логики (True, False, Unknown). ЛЕВОЕ СОЕДИНЕНИЕ лишено этого недостатка.

Производительность: При больших объемах данных во вложенном запросе СУБД может не справиться с оптимизацией. В некоторых случаях (особенно в старых версиях СУБД или на специфических настройках PostgreSQL) запрос может перейти в режим вложенного цикла (Nested Loops), что приведет к катастрофическому замедлению работы.

Способ 3: Оптимальный вариант через индексированные временные таблицы

Если мы работаем с большими массивами данных, "грамотность" решения измеряется его стабильностью. Давайте проанализируем ситуацию: что если таблица "Лысых" — это сложный запрос или результат выборки из большой таблицы? В этом случае лучше всего использовать временные таблицы с индексацией.

Рассмотрим шаги реализации:

  1. Помещаем список исключений во временную таблицу.
  2. Индексируем поле, по которому будет идти сравнение.
  3. Выполняем левое соединение.

// Шаг 1: Подготовка исключений
ВЫБРАТЬ
    ТаблицаЛысых.ФизЛицо КАК ФизЛицо
ПОМЕСТИТЬ ВТ_Исключения
ИЗ
    ТаблицаЛысых КАК ТаблицаЛысых
;
// Шаг 2: Индексация (автоматически или явно)
ИНДЕКСИРОВАТЬ ПО
    ФизЛицо
;
// Шаг 3: Основной запрос
ВЫБРАТЬ
    ТаблицаВсех.ФизЛицо
ИЗ
    ТаблицаВсех КАК ТаблицаВсех
ЛЕВОЕ СОЕДИНЕНИЕ
    ВТ_Исключения КАК ВТ_Исключения
ПО
    ТаблицаВсех.ФизЛицо = ВТ_Исключения.ФизЛицо
ГДЕ
    ВТ_Исключения.ФизЛицо ЕСТЬ NULL

Этот метод гарантирует, что СУБД выберет наиболее эффективный алгоритм соединения (например, Merge Join или Hash Match), так как у нее будет точная статистика по количеству строк и индексы для быстрого поиска.

Способ 4: Метод объединения (UNION ALL) и группировки

Существует еще один "хитрый" способ решить задачу без использования соединений и оператора В. Он может быть полезен в специфических задачах, когда нужно сравнить данные из нескольких источников одновременно.

Разберем логику: мы объединяем две таблицы, добавляя признак (вес) каждой строке. Для основной таблицы вес 0, для таблицы исключений — 1. Затем группируем результат и отсекаем те записи, где сумма весов больше нуля.


ВЫБРАТЬ
    Подзапрос.ФизЛицо
ИЗ
    (ВЫБРАТЬ
        ТаблицаВсех.ФизЛицо КАК ФизЛицо,
        0 КАК Вес
    ИЗ
        ТаблицаВсех
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        ТаблицаЛысых.ФизЛицо,
        1
    ИЗ
        ТаблицаЛысых) КАК Подзапрос
СГРУППИРОВАТЬ ПО
    Подзапрос.ФизЛицо
ИМЕЮЩИЕ
    СУММА(Подзапрос.Вес) = 0

Этот метод интересен тем, что он вообще не использует связи, что иногда помогает обойти проблемы блокировок или неэффективных планов при сложных условиях связи. Однако он требует больше памяти для выполнения группировки.

Технический анализ: Что происходит в СУБД?

Важно понимать, что современные оптимизаторы MS SQL Server часто транслируют и LEFT JOIN + IS NULL, и NOT EXISTS (который является аналогом оператора В) в одну и ту же операцию — Left Anti Semi Join. Это специальный тип соединения, который останавливает поиск соответствия в правой таблице, как только найдена первая подходящая строка. Это очень быстро.

Однако, как мы выяснили выше, оператор НЕ В сложнее для оптимизации из-за логики NULL. Поэтому, выбирая Левое соединение, вы помогаете платформе 1С и СУБД построить максимально эффективный план выполнения запроса.

Подведем итоги

Проанализировав все варианты, мы можем сделать следующие выводы:

Следуя этим рекомендациям, вы напишете не просто работающий, а по-настоящему профессиональный и масштабируемый код на платформе 1С.

← На главную