Как сравнить две таблицы значений и найти строки, которые есть в первой таблице, но отсутствуют во второй?

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

При разработке в среде 1С часто возникает типовая прикладная задача: необходимо сопоставить две таблицы значений (ТаблицаЗначений) и получить только те элементы, которые присутствуют в ТЗ1, но отсутствуют в ТЗ2 (разность множеств). Рассмотрим подробно основные подходы к решению этой задачи: с использованием языка запросов и программных механизмов работы с коллекциями в оперативной памяти.

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

Это классический и наиболее универсальный метод на платформе 1С. Разберем по шагам логику работы: мы передаем обе таблицы в качестве параметров запроса во временные таблицы, выполняем левое соединение ТЗ1 с ТЗ2 по ключевому полю (например, Товар) и отбираем те записи, для которых значение из второй таблицы равно NULL (то есть совпадение не было найдено).

Важное требование: при передаче таблицы значений в запрос все используемые колонки обязательно должны иметь явно заданное описание типов (ОписаниеТипов), иначе платформа вызовет ошибку во время выполнения запроса.

Посмотрим на пример реализации:


&НаСервере
Функция ПолучитьРазницуТаблицЧерезЗапрос(ТЗ1, ТЗ2)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Таб1.Товар КАК Товар
        |ПОМЕСТИТЬ ВТ_ТЗ1
        |ИЗ
        |    &ТЗ1 КАК Таб1
        |ИНДЕКСИРОВАТЬ ПО
        |    Товар
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    Таб2.Товар КАК Товар
        |ПОМЕСТИТЬ ВТ_ТЗ2
        |ИЗ
        |    &ТЗ2 КАК Таб2
        |ИНДЕКСИРОВАТЬ ПО
        |    Товар
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    ВТ_ТЗ1.Товар КАК Товар
        |ИЗ
        |    ВТ_ТЗ1 КАК ВТ_ТЗ1
        |    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ТЗ2 КАК ВТ_ТЗ2
        |    ПО ВТ_ТЗ1.Товар = ВТ_ТЗ2.Товар
        |ГДЕ
        |    ВТ_ТЗ2.Товар ЕСТЬ NULL";
    
    Запрос.УстановитьПараметр("ТЗ1", ТЗ1);
    Запрос.УстановитьПараметр("ТЗ2", ТЗ2);
    
    РезультатЗапроса = Запрос.Выполнить();
    Возврат РезультатЗапроса.Выгрузить();

КонецФункции

Обратим внимание на следующие детали запроса:

  1. Использование секции ИНДЕКСИРОВАТЬ ПО ускоряет выполнение операции соединения во временных таблицах СУБД.
  2. В условии соединения проверяется равенство ключевых полей: ПО ВТ_ТЗ1.Товар = ВТ_ТЗ2.Товар.
  3. В секции ГДЕ используется условие ВТ_ТЗ2.Товар ЕСТЬ NULL (или IS NULL), так как прямое сравнение через оператор равенства = NULL некорректно.

Способ 2. Оптимальный поиск в памяти через объект «Соответствие»

Если таблицы формируются динамически в оперативной памяти сервера 1С и содержат от нескольких сотен до десятков тысяч строк, передача данных в СУБД через временные таблицы может создавать избыточные накладные расходы на сериализацию данных. В таких случаях эффективнее выполнить сопоставление непосредственно в памяти через объект Соответствие.

Проанализируем алгоритм: мы помещаем все уникальные значения из ТЗ2 в коллекцию Соответствие в качестве ключей. Поиск по ключу в Соответствие выполняется за константное время, что исключает квадратичную сложность вложенных циклов.


&НаСервере
Функция ПолучитьРазницуТаблицЧерезСоответствие(ТЗ1, ТЗ2)
    
    // 1. Создаем результирующую таблицу с той же структурой колонок
    ТЗ_Результат = ТЗ1.СкопироватьКолонки();
    
    // 2. Заполняем хеш-таблицу ключами из второй таблицы
    ХешТЗ2 = Новый Соответствие;
    Для Каждого СтрТЗ2 Из ТЗ2 Цикл
        ХешТЗ2.Вставить(СтрТЗ2.Товар, Истина);
    КонецЦикла;
    
    // 3. Обходим первую таблицу и отбираем отсутствующие строки
    Для Каждого СтрТЗ1 Из ТЗ1 Цикл
        Если ХешТЗ2[СтрТЗ1.Товар] = Неопределено Тогда
            НоваяСтр = ТЗ_Результат.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтр, СтрТЗ1);
        КонецЕсли;
    КонецЦикла;
    
    Возврат ТЗ_Результат;

КонецФункции

Способ 3. Индексация таблицы значений и поиск методом НайтиСтроки() или Найти()

Если требуется обработать исходную таблицу без создания нового объекта запроса или промежуточного Соответствие, можно воспользоваться встроенными методами поиска коллекции ТаблицаЗначений. Чтобы поиск по таблице не сканировал строки последовательно, обязательно создадим индекс по ключевой колонке.


&НаСервере
Процедура УдалитьДублиИзПервойТаблицы(ТЗ1, ТЗ2)
    
    // Индексируем колонку для ускорения поиска
    ТЗ2.Индексы.Добавить("Товар");
    
    Отбор = Новый Структура("Товар");
    
    // Выполняем обход с конца к началу для корректного удаления строк по индексу
    Для Индекс = ТЗ1.Количество() - 1 По 0 Цикл
        СтрокаТЗ1 = ТЗ1[Индекс];
        Отбор.Товар = СтрокаТЗ1.Товар;
        
        НайденныеСтроки = ТЗ2.НайтиСтроки(Отбор);
        Если НайденныеСтроки.Количество() > 0 Тогда
            ТЗ1.Удалить(Индекс);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

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

Выводы и рекомендации по выбору способа

  1. Если таблицы значений уже получены в виде результатов запроса или требуют сложного сопоставления по нескольким полям с агрегацией — используйте Способ 1 (Запрос с ЛЕВОЕ СОЕДИНЕНИЕ и ЕСТЬ NULL).
  2. Если требуется максимальная производительность при сравнении коллекций в оперативной памяти на сервере без лишних обращений к СУБД — используйте Способ 2 (Соответствие).
  3. При использовании поиска через НайтиСтроки() всегда предварительно добавляйте индекс в коллекцию Индексы таблицы значений.
← На главную