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