Как в запросе 1С выбрать записи из одной таблицы, которых нет во второй: эффективные способы

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

В практике разработчика 1С часто возникает классическая задача: необходимо сравнить две таблицы (или результата двух выборок) и получить только те данные из первой таблицы, для которых нет соответствия во второй. В математике эта операция называется разностью множеств. На языке SQL и запросов 1С это можно реализовать несколькими способами, каждый из которых имеет свои особенности с точки зрения читаемости кода и скорости выполнения.

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

Способ 1. Классическое левое соединение с проверкой на NULL

Это самый распространенный и рекомендуемый метод в среде 1С. Суть его заключается в том, что мы выполняем ЛЕВОЕ СОЕДИНЕНИЕ первой таблицы со второй по ключевому полю. Если соответствие во второй таблице не найдено, то все поля этой таблицы в результирующем наборе данных будут содержать значение NULL.

Рассмотрим пример кода, который решает задачу через секцию ГДЕ:


ВЫБРАТЬ
    Таблица1.ФИО КАК ФИО
ИЗ
    Таблица1 КАК Таблица1
        ЛЕВОЕ СОЕДИНЕНИЕ Таблица2 КАК Таблица2
        ПО Таблица1.ФИО = Таблица2.ФИО
ГДЕ
    Таблица2.ФИО ЕСТЬ NULL

Как это работает: база данных пытается сопоставить каждую строку из Таблица1 со строками из Таблица2. Если совпадений нет, Таблица2.ФИО становится NULL. Условие в секции ГДЕ отсекает все строки, где совпадение было найдено. Проанализируем ситуацию: этот метод считается универсальным, так как современные СУБД (MS SQL Server, PostgreSQL) умеют оптимизировать такие запросы в эффективную операцию Anti-Join.

Способ 2. Использование условия НЕ В (подзапрос)

Иногда разработчики предпочитают использовать конструкцию ГДЕ НЕ Поле В (...). Это выглядит более наглядно с точки зрения человеческой логики: "выбрать всё, чего нет в списке".

Разберем пример реализации:


ВЫБРАТЬ
    Таблица1.ФИО
ИЗ
    Таблица1 КАК Таблица1
ГДЕ
    НЕ Таблица1.ФИО В
            (ВЫБРАТЬ
                Таблица2.ФИО
            ИЗ
                Таблица2 КАК Таблица2)

Важное замечание: в некоторых случаях, особенно при работе с индексированными полями в справочниках или регистрах, этот метод может показывать очень высокую скорость. Однако стоит помнить, что если в подзапросе (во второй таблице) окажется значение NULL, поведение классического SQL-запроса может измениться. В языке запросов 1С эта конструкция обычно транслируется корректно, но при работе с огромными списками (десятки и сотни тысяч записей) рекомендуется предварительно помещать данные второй таблицы во временную таблицу с индексом.

Способ 3. Обход условий через внутреннее соединение (хитрость конструктора)

Иногда перед разработчиком ставится специфическая задача: выполнить отсечение записей, используя только закладку "Связи" в конструкторе запросов, не заходя на закладку "Условия". Это может быть полезно для динамического формирования запросов или при создании сложных программных фильтров.

Для этого мы можем использовать "хитрый" прием: создаем внутреннее соединение с виртуальной таблицей, состоящей из одной записи, и переносим условие фильтрации прямо в параметры связи ПО.

Проанализируем пример такого изящного решения:


ВЫБРАТЬ 
    Таблица1.ФИО
ИЗ
    Таблица1 КАК Таблица1
        ЛЕВОЕ СОЕДИНЕНИЕ Таблица2 КАК Таблица2
        ПО Таблица1.ФИО = Таблица2.ФИО
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 1 КАК Поле) КАК Фильтр
        ПО Таблица2.ФИО ЕСТЬ NULL

В данном случае ВНУТРЕННЕЕ СОЕДИНЕНИЕ с таблицей Фильтр сработает как жесткий ограничитель. Поскольку условие Таблица2.ФИО ЕСТЬ NULL прописано в секции ПО, лишние записи будут отсечены на этапе сборки соединений. Это позволяет добиться результата без использования стандартной секции ГДЕ.

Способ 4. Метод объединения и группировки (алгебраический подход)

Если нам нужно найти разность таблиц, мы можем объединить их, пометить каждую запись специальным маркером, а затем сгруппировать результат. Этот метод бывает крайне эффективен, если индексы по каким-то причинам не могут быть использованы или если таблицы очень широкие.

Рассмотрим алгоритм действий:

  1. Выбираем все записи из первой таблицы и добавляем числовое поле Контроль со значением 1.
  2. Выбираем все записи из второй таблицы и добавляем поле Контроль со значением, например, 10 (или просто используем ОБЪЕДИНИТЬ без ВСЕ для удаления дублей, но с суммами надежнее).
  3. Группируем результат по ключевому полю.
  4. Отбираем только те записи, где сумма Контроль осталась равной 1.

Пример кода с временными таблицами:


ВЫБРАТЬ
    Таблица1.ФИО КАК ФИО,
    1 КАК Счетчик
ПОМЕСТИТЬ втВсеЗаписи
ИЗ
    Таблица1 КАК Таблица1

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Таблица2.ФИО,
    10
ИЗ
    Таблица2 КАК Таблица2
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    втВсеЗаписи.ФИО
ИЗ
    втВсеЗаписи КАК втВсеЗаписи
ГРУППИРОВАТЬ ПО
    втВсеЗаписи.ФИО
ИМЕЮЩИЕ
    СУММА(втВсеЗаписиСчетчик) = 1

Что оптимальнее: советы по производительности

Выясним причину, по которой один запрос может работать быстрее другого. В мире 1С всё сильно зависит от того, как СУБД построит план запроса. Рассмотрим основные правила:

Таким образом, мы разобрали основные механики работы с разностью множеств в 1С. Для большинства повседневных задач используйте Способ 1. Если вы сталкиваетесь с необъяснимыми тормозами при соединении больших справочников, попробуйте Способ 2. А для "спортивного интереса" или обхода ограничений конструктора теперь в вашем арсенале есть методы с внутренними соединениями и группировками.

← На главную