При работе с объектом ТаблицаЗначений начинающие разработчики часто сталкиваются с логической ловушкой (поможет инструмент для пошаговой отладки кода в 1С) при попытке удаления строк в цикле. Типичная ситуация: мы обходим таблицу через Для каждого, находим строку, соответствующую условию, и вызываем метод Удалить(). В этот момент индекс текущей строки смещается, и следующая за ней строка «проскакивает» мимо проверки. В итоге часть данных, подлежащих удалению, остается в таблице.
В этой статье мы подробно разберем, почему это происходит, и проанализируем все эффективные способы решения этой задачи — от классических циклов до профессиональных методов с использованием запросов.
Разберем ситуацию изнутри. Когда мы используем конструкцию Для каждого Строка из ТаблицаЗначений, платформа 1С обращается к строкам по их внутреннему порядковому номеру. Представим, что нам нужно удалить 3-ю строку. Как только мы выполняем Удалить(Строка), бывшая 4-я строка моментально становится 3-й. Однако счетчик цикла уже переходит к следующему шагу и обращается к «новой» 4-й строке (которая раньше была 5-й). Таким образом, та строка, что сдвинулась на место удаленной, никогда не будет проверена. Это классическая ошибка, приводящая к некорректным результатам обработки данных.
Это самый простой и надежный способ, если вы хотите использовать обход по индексу. Разберем логику: если мы начинаем удаление с последней строки и двигаемся к первой, то при удалении текущей строки индексы тех строк, которые нам еще только предстоит проверить (расположенных выше), не меняются. Сдвигаются только те строки, которые мы уже обработали.
Рассмотрим пример реализации:
ВсегоСтрок = ТаблицаЗначенийДляФорматирования.Количество();
Для Счетчик = 1 По ВсегоСтрок Цикл
// Вычисляем индекс с конца
ТекущийИндекс = ВсегоСтрок - Счетчик;
СтрокаТЗ = ТаблицаЗначенийДляФорматирования[ТекущийИндекс];
Если СтрокаТЗ.КоличествоУпаковок = 0 Тогда
ТаблицаЗначенийДляФорматирования.Удалить(ТекущийИндекс);
КонецЕсли;
КонецЦикла;
Важно: Обратите внимание, что индексы в 1С начинаются с 0, поэтому мы вычитаем счетчик из общего количества. Этот метод работает быстро и не требует выделения дополнительной памяти под вспомогательные объекты.
Этот метод считается наиболее «безопасным» и читаемым с точки зрения стиля программирования 1С. Суть его заключается в том, чтобы сначала собрать ссылки на все строки, подлежащие удалению, в отдельный массив, а затем вторым циклом удалить их из таблицы.
Разберем по шагам:
СтрокиДляУдаления.Для каждого.Пример кода:
МассивСтрок = Новый Массив;
// Шаг 1: Поиск строк
Для каждого СтрокаТЗ из ТаблицаЗначенийДляФорматирования Цикл
Если СтрокаТЗ.КоличествоУпаковок = 0 Тогда
МассивСтрок.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Шаг 2: Удаление найденных строк
Для каждого СтрокаИзМассива из МассивСтрок Цикл
ТаблицаЗначенийДляФорматирования.Удалить(СтрокаИзМассива);
КонецЦикла;
Преимущество этого метода в том, что при удалении по прямой ссылке на объект (строку) нам не нужно беспокоиться об индексах. Ссылка остается валидной до момента удаления объекта.
Этот подход позволяет обойти таблицу за один проход, не используя дополнительные массивы и не переворачивая цикл. Мы используем переменную-индекс и увеличиваем ее только в том случае, если строка не была удалена.
Проанализируем ситуацию:
Индекс = 0;
Пока Индекс < ТаблицаЗначенийДляФорматирования.Количество() Цикл
СтрокаТЗ = ТаблицаЗначенийДляФорматирования[Индекс];
Если СтрокаТЗ.КоличествоУпаковок = 0 Тогда
ТаблицаЗначенийДляФорматирования.Удалить(Индекс);
// Индекс не увеличиваем! На это место встала следующая строка.
Иначе
// Переходим к следующей строке, только если текущую не удаляли.
Индекс = Индекс + 1;
КонецЕсли;
КонецЦикла;
Этот метод очень эффективен, так как требует минимального количества операций и переменных.
Если ваше условие удаления — это простое равенство (например, КоличествоУпаковок = 0), можно воспользоваться встроенным методом НайтиСтроки(). Этот метод возвращает массив строк, который мы сразу можем использовать для удаления.
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("КоличествоУпаковок", 0);
НайденныеСтроки = ТаблицаЗначенийДляФорматирования.НайтиСтроки(ПараметрыОтбора);
Для каждого СтрокаДляУдаления из НайденныеСтроки Цикл
ТаблицаЗначенийДляФорматирования.Удалить(СтрокаДляУдаления);
КонецЦикла;
Это самый лаконичный код, однако он ограничен условиями, которые можно поместить в Структура (только равенство). При необходимости можно воспользоваться статьей о безопасном использовании метода НайтиСтроки.
Иногда вместо удаления «плохих» строк проще и быстрее отобрать «хорошие» строки в новую таблицу. Это особенно актуально, если удаляемых строк очень много (например, более 80% таблицы).
Посмотрим на пример:
// Копируем структуру колонок
НоваяТЗ = ТаблицаЗначенийДляФорматирования.СкопироватьКолонки();
Для каждого СтрокаТЗ из ТаблицаЗначенийДляФорматирования Цикл
Если СтрокаТЗ.КоличествоУпаковок <> 0 Тогда
ЗаполнитьЗначенияСвойств(НоваяТЗ.Добавить(), СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Заменяем старую таблицу новой
ТаблицаЗначенийДляФорматирования = НоваяТЗ;
Этот метод позволяет избежать постоянного перестроения индексов таблицы при каждом вызове Удалить().
Если ваша ТаблицаЗначений содержит десятки или сотни тысяч строк, перебор в цикле (любым из вышеописанных способов) может занять значительное время. В платформе 1С работа с наборами данных в памяти через механизм запросов оптимизирована гораздо лучше.
Рассмотрим алгоритм оптимизации:
ГДЕ КоличествоУпаковок <> 0.ТаблицаЗначений.Важный нюанс: Если у вашей таблицы значений настроены индексы (объект Индексы), каждое удаление строки заставляет платформу пересчитывать их. При массовом удалении рекомендуется сначала удалить все индексы у ТЗ, произвести очистку, а затем создать их заново.
Для небольших таблиц (до 1000 строк) оптимальным выбором будет цикл с конца или вспомогательный массив. Они просты в написании и отладке. Для больших объемов данных или сложных условий фильтрации всегда рассматривайте возможность использования запросов или метода фильтрации через копирование. Выбор правильного метода не только решит проблему пропуска строк, но и обеспечит высокую производительность вашего решения.