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

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

В процессе разработки на платформе 1С часто возникает задача синхронизации двух табличных частей (ТЧ) одного документа или обработки. Типичный пример: у нас есть "ТЧ1" (эталонный список номенклатуры с остатками) и "ТЧ2" (список с ценами). Нам необходимо оставить в "ТЧ2" только те позиции, которые присутствуют в "ТЧ1", удалив всё лишнее. В этой статье мы подробно разберем несколько эффективных способов решения этой задачи: от простых циклов до оптимизированных запросов.

Метод 1: Полное перезаполнение через запрос (Внутреннее соединение)

Этот метод считается одним из самых надежных и простых в реализации, если табличная часть не содержит специфических данных, которые нельзя пересоздавать (например, уникальные идентификаторы строк, связанные с другими объектами). Суть заключается в том, чтобы не удалять строки по одной, а получить готовый результирующий набор данных и загрузить его целиком.

Рассмотрим по шагам реализацию этого подхода. Мы выгружаем обе табличные части в запрос, соединяем их по ключевым полям (номенклатура и характеристика) и выгружаем результат обратно.


// Предположим, ТЧ1 и ТЧ2 — это наши табличные части
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЧ1.Номенклатура КАК Номенклатура,
    |   ТЧ1.Характеристика КАК Характеристика
    |ПОМЕСТИТЬ ВТ_Эталон
    |ИЗ
    |   &ТЧ1 КАК ТЧ1
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ТЧ2.Номенклатура КАК Номенклатура,
    |   ТЧ2.Характеристика КАК Характеристика,
    |   ТЧ2.Цена КАК Цена
    |ПОМЕСТИТЬ ВТ_Данные
    |ИЗ
    |   &ТЧ2 КАК ТЧ2
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   ВТ_Данные.Номенклатура,
    |   ВТ_Данные.Характеристика,
    |   ВТ_Данные.Цена
    |ИЗ
    |   ВТ_Данные КАК ВТ_Данные
    |       ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Эталон КАК ВТ_Эталон
    |       ПО ВТ_Данные.Номенклатура = ВТ_Эталон.Номенклатура
    |           И ВТ_Данные.Характеристика = ВТ_Эталон.Характеристика";

Запрос.УстановитьПараметр("ТЧ1", ТЧ1.Выгрузить());
Запрос.УстановитьПараметр("ТЧ2", ТЧ2.Выгрузить());

РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
    ТЧ2.Загрузить(РезультатЗапроса.Выгрузить());
Иначе
    ТЧ2.Очистить();
КонецЕсли;

Важный нюанс: Обратите внимание на использование оператора ВЫБРАТЬ РАЗЛИЧНЫЕ. Если в эталонной таблице (ТЧ1) одна и та же номенклатура встречается несколько раз, обычное ВНУТРЕННЕЕ СОЕДИНЕНИЕ может "размножить" строки в ТЧ2. Использование РАЗЛИЧНЫЕ или предварительное сворачивание ТЧ1 исключает эту проблему.

Метод 2: Удаление по индексам через обратный запрос

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

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЧ1.Номенклатура КАК Номенклатура,
    |   ТЧ1.Характеристика КАК Характеристика
    |ПОМЕСТИТЬ ВТ_Эталон
    |ИЗ
    |   &ТЧ1 КАК ТЧ1
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ТЧ2.НомерСтроки - 1 КАК Индекс
    |ИЗ
    |   &ТЧ2 КАК ТЧ2
    |       ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Эталон КАК ВТ_Эталон
    |       ПО ТЧ2.Номенклатура = ВТ_Эталон.Номенклатура
    |           И ТЧ2.Характеристика = ВТ_Эталон.Характеристика
    |ГДЕ
    |   ВТ_Эталон.Номенклатура ЕСТЬ NULL
    |
    |УПОРЯДОЧИТЬ ПО
    |   Индекс УБЫВ";

Запрос.УстановитьПараметр("ТЧ1", ТЧ1.Выгрузить());
// Для получения НомерСтроки в запросе нужно передавать именно как Таблицу Значений
Запрос.УстановитьПараметр("ТЧ2", ТЧ2.Выгрузить()); 

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    ТЧ2.Удалить(Выборка.Индекс);
КонецЦикла;

Метод 3: Использование объекта «Соответствие» (Map) для оптимизации поиска

Если данные находятся в памяти и использование запроса кажется избыточным (например, при работе с небольшими объемами на стороне клиента или в специфических циклах), метод с вложенными циклами и функцией НайтиСтроки() может работать медленно. Проблема в том, что НайтиСтроки() при каждом вызове сканирует таблицу, что дает сложность алгоритма O(N*M).

Рассмотрим более производительный вариант с использованием объекта Соответствие. Поиск в нем происходит практически мгновенно, независимо от количества элементов.

  1. Создаем Соответствие, где ключом будет уникальная комбинация (КлючСвязи), а значением — просто Истина.
  2. Проходим одним циклом по ТЧ2 и проверяем наличие ключа в соответствии.
  3. Если ключа нет — добавляем строку в массив для удаления.
  4. Вторым циклом удаляем строки из ТЧ.

// 1. Формируем эталонное соответствие
КлючиЭталона = Новый Соответствие;
Для Каждого Стр1 Из ТЧ1 Цикл
    // Создаем уникальный ключ (например, через структуру или строку)
    Ключ = Стр1.Номенклатура.УникальныйИдентификатор() + "_" + Стр1.Характеристика.УникальныйИдентификатор();
    КлючиЭталона.Вставить(Ключ, Истина);
КонецЦикла;

// 2. Определяем строки на удаление
СтрокиДляУдаления = Новый Массив;
Для Каждого Стр2 Из ТЧ2 Цикл
    КлючПоиска = Стр2.Номенклатура.УникальныйИдентификатор() + "_" + Стр2.Характеристика.УникальныйИдентификатор();
    Если КлючиЭталона.Получить(КлючПоиска) = Неопределено Тогда
        СтрокиДляУдаления.Добавить(Стр2);
    КонецЕсли;
КонецЦикла;

// 3. Удаляем собранные строки
Для Каждого СтрУдаления Из СтрокиДляУдаления Цикл
    ТЧ2.Удалить(СтрУдаления);
КонецЦикла;

Преимущество этого метода: нам не нужно заботиться об индексах и сортировке "с конца", так как метод Удалить() может принимать сам объект строки табличной части.

Метод 4: Работа через Таблицу Значений и индексы

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

Выясним, как это реализовать:


ТЗ_Цены = ТЧ2.Выгрузить();
ТЗ_Остатки = ТЧ1.Выгрузить();

// Добавляем индекс для моментального поиска
ТЗ_Остатки.Индексы.Добавить("Номенклатура, Характеристика");

ТЗ_Цены.Колонки.Добавить("Актуально", Новый ОписаниеТипов("Булево"));

Для Каждого Стр Из ТЗ_Цены Цикл
    ПараметрыОтбора = Новый Структура("Номенклатура, Характеристика", Стр.Номенклатура, Стр.Характеристика);
    НайденныеСтроки = ТЗ_Остатки.НайтиСтроки(ПараметрыОтбора);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        Стр.Актуально = Истина;
    КонецЕсли;
КонецЦикла;

// Фильтруем таблицу и загружаем обратно
ПараметрыВыбора = Новый Структура("Актуально", Истина);
ТЗ_Результат = ТЗ_Цены.Скопировать(ПараметрыВыбора);

ТЧ2.Загрузить(ТЗ_Результат);

Общие рекомендации и правила

Проанализировав все способы, выделим ключевые правила эффективной разработки:

Выбор конкретного метода зависит от ваших задач, но наиболее универсальным и производительным для платформы 8.3 остается Вариант 1 (Запрос с внутренним соединением) из-за его лаконичности и высокой скорости работы на стороне СУБД.

← На главную