В практике разработки на платформе 1С:Предприятие часто возникает классическая задача: необходимо выбрать записи из одной таблицы, которых гарантированно нет в другой. Типичный пример — выбор "не лысых" людей из общего списка, при наличии отдельной таблицы "лысых". Начинающие и опытные разработчики часто спорят, какой метод является более "грамотным", производительным и стабильным. Давайте подробно разберем основные способы реализации этой задачи, проанализируем их плюсы и минусы, а также выясним, что происходит "под капотом" СУБД.
Этот метод считается стандартным и наиболее рекомендуемым в среде 1С. Суть его заключается в том, что мы соединяем основную таблицу со вспомогательной (черным списком) по ключевому полю. Если соответствие не найдено, поля из правой таблицы заполняются значением NULL. Именно эти строки нам и нужны.
Рассмотрим пример кода:
ВЫБРАТЬ
ТаблицаВсех.ФизЛицо КАК ФизЛицо
ИЗ
ТаблицаВсех КАК ТаблицаВсех
ЛЕВОЕ СОЕДИНЕНИЕ
ТаблицаЛысых КАК ТаблицаЛысых
ПО
ТаблицаВсех.ФизЛицо = ТаблицаЛысых.ФизЛицо
ГДЕ
ТаблицаЛысых.ФизЛицо ЕСТЬ NULL
Преимущества этого подхода:
Этот вариант кажется более интуитивно понятным с точки зрения человеческой логики: "выбрать всех, кто НЕ В списке". Однако за этой простотой скрываются серьезные технические риски.
Рассмотрим реализацию:
ВЫБРАТЬ
ТаблицаВсех.ФизЛицо
ИЗ
ТаблицаВсех
ГДЕ
НЕ ТаблицаВсех.ФизЛицо В (ВЫБРАТЬ ТаблицаЛысых.ФизЛицо ИЗ ТаблицаЛысых)
Почему же опытные разработчики называют конструкцию НЕ В "кошмаром"? Разберем причины по шагам:
Проблема NULL в подзапросе: Это самый коварный момент. Согласно логике SQL, если вложенный запрос вернет хотя бы одну строку со значением NULL, то весь оператор НЕ В вернет пустой результат. Ваша выборка будет пустой, даже если в основной таблице тысячи записей. Это происходит из-за трехзначной логики (True, False, Unknown). ЛЕВОЕ СОЕДИНЕНИЕ лишено этого недостатка.
Производительность: При больших объемах данных во вложенном запросе СУБД может не справиться с оптимизацией. В некоторых случаях (особенно в старых версиях СУБД или на специфических настройках PostgreSQL) запрос может перейти в режим вложенного цикла (Nested Loops), что приведет к катастрофическому замедлению работы.
Если мы работаем с большими массивами данных, "грамотность" решения измеряется его стабильностью. Давайте проанализируем ситуацию: что если таблица "Лысых" — это сложный запрос или результат выборки из большой таблицы? В этом случае лучше всего использовать временные таблицы с индексацией.
Рассмотрим шаги реализации:
// Шаг 1: Подготовка исключений
ВЫБРАТЬ
ТаблицаЛысых.ФизЛицо КАК ФизЛицо
ПОМЕСТИТЬ ВТ_Исключения
ИЗ
ТаблицаЛысых КАК ТаблицаЛысых
;
// Шаг 2: Индексация (автоматически или явно)
ИНДЕКСИРОВАТЬ ПО
ФизЛицо
;
// Шаг 3: Основной запрос
ВЫБРАТЬ
ТаблицаВсех.ФизЛицо
ИЗ
ТаблицаВсех КАК ТаблицаВсех
ЛЕВОЕ СОЕДИНЕНИЕ
ВТ_Исключения КАК ВТ_Исключения
ПО
ТаблицаВсех.ФизЛицо = ВТ_Исключения.ФизЛицо
ГДЕ
ВТ_Исключения.ФизЛицо ЕСТЬ NULL
Этот метод гарантирует, что СУБД выберет наиболее эффективный алгоритм соединения (например, Merge Join или Hash Match), так как у нее будет точная статистика по количеству строк и индексы для быстрого поиска.
Существует еще один "хитрый" способ решить задачу без использования соединений и оператора В. Он может быть полезен в специфических задачах, когда нужно сравнить данные из нескольких источников одновременно.
Разберем логику: мы объединяем две таблицы, добавляя признак (вес) каждой строке. Для основной таблицы вес 0, для таблицы исключений — 1. Затем группируем результат и отсекаем те записи, где сумма весов больше нуля.
ВЫБРАТЬ
Подзапрос.ФизЛицо
ИЗ
(ВЫБРАТЬ
ТаблицаВсех.ФизЛицо КАК ФизЛицо,
0 КАК Вес
ИЗ
ТаблицаВсех
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТаблицаЛысых.ФизЛицо,
1
ИЗ
ТаблицаЛысых) КАК Подзапрос
СГРУППИРОВАТЬ ПО
Подзапрос.ФизЛицо
ИМЕЮЩИЕ
СУММА(Подзапрос.Вес) = 0
Этот метод интересен тем, что он вообще не использует связи, что иногда помогает обойти проблемы блокировок или неэффективных планов при сложных условиях связи. Однако он требует больше памяти для выполнения группировки.
Важно понимать, что современные оптимизаторы MS SQL Server часто транслируют и LEFT JOIN + IS NULL, и NOT EXISTS (который является аналогом оператора В) в одну и ту же операцию — Left Anti Semi Join. Это специальный тип соединения, который останавливает поиск соответствия в правой таблице, как только найдена первая подходящая строка. Это очень быстро.
Однако, как мы выяснили выше, оператор НЕ В сложнее для оптимизации из-за логики NULL. Поэтому, выбирая Левое соединение, вы помогаете платформе 1С и СУБД построить максимально эффективный план выполнения запроса.
Проанализировав все варианты, мы можем сделать следующие выводы:
NULL. Это стандарт де-факто в 1С.NULL или внезапное замедление запроса в десятки раз не оправдывают лаконичность кода.Следуя этим рекомендациям, вы напишете не просто работающий, а по-настоящему профессиональный и масштабируемый код на платформе 1С.