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