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