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