В процессе разработки на платформе 1С часто возникает задача синхронизации двух табличных частей (ТЧ) одного документа или обработки. Типичный пример: у нас есть "ТЧ1" (эталонный список номенклатуры с остатками) и "ТЧ2" (список с ценами). Нам необходимо оставить в "ТЧ2" только те позиции, которые присутствуют в "ТЧ1", удалив всё лишнее. В этой статье мы подробно разберем несколько эффективных способов решения этой задачи: от простых циклов до оптимизированных запросов.
Этот метод считается одним из самых надежных и простых в реализации, если табличная часть не содержит специфических данных, которые нельзя пересоздавать (например, уникальные идентификаторы строк, связанные с другими объектами). Суть заключается в том, чтобы не удалять строки по одной, а получить готовый результирующий набор данных и загрузить его целиком.
Рассмотрим по шагам реализацию этого подхода. Мы выгружаем обе табличные части в запрос, соединяем их по ключевым полям (номенклатура и характеристика) и выгружаем результат обратно.
// Предположим, ТЧ1 и ТЧ2 — это наши табличные части
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ1.Номенклатура КАК Номенклатура,
| ТЧ1.Характеристика КАК Характеристика
|ПОМЕСТИТЬ ВТ_Эталон
|ИЗ
| &ТЧ1 КАК ТЧ1
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ2.Номенклатура КАК Номенклатура,
| ТЧ2.Характеристика КАК Характеристика,
| ТЧ2.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЧ2 КАК ТЧ2
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТ_Данные.Номенклатура,
| ВТ_Данные.Характеристика,
| ВТ_Данные.Цена
|ИЗ
| ВТ_Данные КАК ВТ_Данные
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Эталон КАК ВТ_Эталон
| ПО ВТ_Данные.Номенклатура = ВТ_Эталон.Номенклатура
| И ВТ_Данные.Характеристика = ВТ_Эталон.Характеристика";
Запрос.УстановитьПараметр("ТЧ1", ТЧ1.Выгрузить());
Запрос.УстановитьПараметр("ТЧ2", ТЧ2.Выгрузить());
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
ТЧ2.Загрузить(РезультатЗапроса.Выгрузить());
Иначе
ТЧ2.Очистить();
КонецЕсли;
Важный нюанс: Обратите внимание на использование оператора ВЫБРАТЬ РАЗЛИЧНЫЕ. Если в эталонной таблице (ТЧ1) одна и та же номенклатура встречается несколько раз, обычное ВНУТРЕННЕЕ СОЕДИНЕНИЕ может "размножить" строки в ТЧ2. Использование РАЗЛИЧНЫЕ или предварительное сворачивание ТЧ1 исключает эту проблему.
Если нам важно именно удалить лишние строки, сохранив порядок или другие свойства существующих строк в ТЧ2, мы можем использовать запрос для поиска индексов строк, подлежащих удалению. Для этого используется ЛЕВОЕ СОЕДИНЕНИЕ, где мы ищем строки, для которых нет пары в эталонной таблице.
Проанализируем этот алгоритм: мы получаем список индексов, сортируем их по убыванию и удаляем строки. Сортировка по убыванию критически важна: если удалять строки с начала, индексы последующих строк сместятся, и мы удалим не те данные или получим ошибку выхода за границы массива.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ1.Номенклатура КАК Номенклатура,
| ТЧ1.Характеристика КАК Характеристика
|ПОМЕСТИТЬ ВТ_Эталон
|ИЗ
| &ТЧ1 КАК ТЧ1
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ2.НомерСтроки - 1 КАК Индекс
|ИЗ
| &ТЧ2 КАК ТЧ2
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Эталон КАК ВТ_Эталон
| ПО ТЧ2.Номенклатура = ВТ_Эталон.Номенклатура
| И ТЧ2.Характеристика = ВТ_Эталон.Характеристика
|ГДЕ
| ВТ_Эталон.Номенклатура ЕСТЬ NULL
|
|УПОРЯДОЧИТЬ ПО
| Индекс УБЫВ";
Запрос.УстановитьПараметр("ТЧ1", ТЧ1.Выгрузить());
// Для получения НомерСтроки в запросе нужно передавать именно как Таблицу Значений
Запрос.УстановитьПараметр("ТЧ2", ТЧ2.Выгрузить());
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ТЧ2.Удалить(Выборка.Индекс);
КонецЦикла;
Если данные находятся в памяти и использование запроса кажется избыточным (например, при работе с небольшими объемами на стороне клиента или в специфических циклах), метод с вложенными циклами и функцией НайтиСтроки() может работать медленно. Проблема в том, что НайтиСтроки() при каждом вызове сканирует таблицу, что дает сложность алгоритма O(N*M).
Рассмотрим более производительный вариант с использованием объекта Соответствие. Поиск в нем происходит практически мгновенно, независимо от количества элементов.
Соответствие, где ключом будет уникальная комбинация (КлючСвязи), а значением — просто Истина.
// 1. Формируем эталонное соответствие
КлючиЭталона = Новый Соответствие;
Для Каждого Стр1 Из ТЧ1 Цикл
// Создаем уникальный ключ (например, через структуру или строку)
Ключ = Стр1.Номенклатура.УникальныйИдентификатор() + "_" + Стр1.Характеристика.УникальныйИдентификатор();
КлючиЭталона.Вставить(Ключ, Истина);
КонецЦикла;
// 2. Определяем строки на удаление
СтрокиДляУдаления = Новый Массив;
Для Каждого Стр2 Из ТЧ2 Цикл
КлючПоиска = Стр2.Номенклатура.УникальныйИдентификатор() + "_" + Стр2.Характеристика.УникальныйИдентификатор();
Если КлючиЭталона.Получить(КлючПоиска) = Неопределено Тогда
СтрокиДляУдаления.Добавить(Стр2);
КонецЕсли;
КонецЦикла;
// 3. Удаляем собранные строки
Для Каждого СтрУдаления Из СтрокиДляУдаления Цикл
ТЧ2.Удалить(СтрУдаления);
КонецЦикла;
Преимущество этого метода: нам не нужно заботиться об индексах и сортировке "с конца", так как метод Удалить() может принимать сам объект строки табличной части.
Если вы предпочитаете работать с объектом ТаблицаЗначений, не забудьте про возможность программного индексирования колонок. Это значительно ускоряет работу метода НайтиСтроки().
Выясним, как это реализовать:
ТЗ_Цены = ТЧ2.Выгрузить();
ТЗ_Остатки = ТЧ1.Выгрузить();
// Добавляем индекс для моментального поиска
ТЗ_Остатки.Индексы.Добавить("Номенклатура, Характеристика");
ТЗ_Цены.Колонки.Добавить("Актуально", Новый ОписаниеТипов("Булево"));
Для Каждого Стр Из ТЗ_Цены Цикл
ПараметрыОтбора = Новый Структура("Номенклатура, Характеристика", Стр.Номенклатура, Стр.Характеристика);
НайденныеСтроки = ТЗ_Остатки.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
Стр.Актуально = Истина;
КонецЕсли;
КонецЦикла;
// Фильтруем таблицу и загружаем обратно
ПараметрыВыбора = Новый Структура("Актуально", Истина);
ТЗ_Результат = ТЗ_Цены.Скопировать(ПараметрыВыбора);
ТЧ2.Загрузить(ТЗ_Результат);
Проанализировав все способы, выделим ключевые правила эффективной разработки:
Для Счетчик = ТЧ.Количество() - 1 По 0 Цикл.Соответствием или Запросом является единственно верным. Обычный поиск НайтиСтроки() в двойном цикле без индексов может заставить пользователя ждать несколько минут.Выбор конкретного метода зависит от ваших задач, но наиболее универсальным и производительным для платформы 8.3 остается Вариант 1 (Запрос с внутренним соединением) из-за его лаконичности и высокой скорости работы на стороне СУБД.