При разработке в среде «1С:Предприятие» программисты регулярно сталкиваются с задачей удаления строк из табличных частей (ТЧ) документов или справочников по определенному условию. Казалось бы, задача тривиальная, но стандартный и интуитивный подход с обходом коллекции «в лоб» через цикл Для каждого гарантированно приводит либо к пропуску строк, либо к ошибкам выполнения.
В этой статье мы подробно разберем, почему возникает популярная ошибка «Недопустимое значение параметра» или выход индекса за пределы массива, проанализируем решения, предложенные участниками форума, а также рассмотрим эталонные, безопасные и наиболее производительные методы удаления строк согласно стандартам разработки фирмы «1С».
Рассмотрим код, с которого началась дискуссия на форуме:
Товары = Объект.Товары.Выгрузить(); // Ошибка 1: Выгрузка в ТЗ ломает логику ТЧ
Для каждого СтрокаТоваров из Товары Цикл
Если СтрокаТоваров.Номенклатура.Код = "00-00005130" Тогда
Объект.Товары.Удалить(СтрокаТоваров.НомерСтроки); // Ошибка 2: Передача НомераСтроки вместо индекса или строки
КонецЕсли;
КонецЦикла;
В этом коротком фрагменте кода скрыто сразу три критические ошибки:
Удалить(): Метод табличной части Удалить() ожидает в качестве параметра либо Индекс строки (целое число от 0 до Количество - 1), либо саму СтрокуТабличнойЧасти. Автор попытался передать туда НомерСтроки. Номер строки в интерфейсе начинается с 1, тогда как индексы в базе начинаются с 0. Это сразу же вызывает ошибку «Недопустимое значение параметра».Для каждого, платформа рассчитывает итератор по исходному состоянию коллекции. Если внутри цикла мы удаляем строку, вся таблица «сдвигается» вверх. Индексы последующих строк уменьшаются на единицу, текущий указатель итератора смещается, из-за чего часть строк просто пропускается без проверки, а в конце цикла система пытается обратиться к несуществующим элементам, вызывая аварийное завершение работы.Объект.Товары. Из-за этого ссылки на строки ТЗ не совпадали с объектами строк реальной табличной части.Давайте выясним, какими способами можно решить эту задачу правильно и эффективно.
Этот способ считается эталонным и самым безопасным. Он исключает любые проблемы со сдвигом индексов в процессе обхода коллекции. Суть метода заключается в разделении задачи на два независимых шага: сначала мы находим и собираем элементы для удаления, а затем последовательно удаляем их.
Рассмотрим по шагам, как это реализовать на практике:
// Шаг 1: Создаем массив, куда запишем ссылки на удаляемые строки
МассивДляУдаления = Новый Массив;
// Проходим по оригинальной табличной части и фиксируем нужные строки
Для Каждого СтрокаТоваров Из Объект.Товары Цикл
// Применяем ваше условие (в данном примере - поиск по кодам номенклатуры)
Если СтрокаТоваров.Номенклатура.Код = "00-00005130"
Или СтрокаТоваров.Номенклатура.Код = "ЦБ-00005141" Тогда
// Добавляем объект строки в массив
МассивДляУдаления.Добавить(СтрокаТоваров);
КонецЕсли;
КонецЦикла;
// Шаг 2: Удаляем собранные строки непосредственно из ТЧ объекта
Для Каждого Строка Из МассивДляУдаления Цикл
Объект.Товары.Удалить(Строка);
КонецЦикла;
Преимущества метода:
Если вы не хотите создавать дополнительные объекты в памяти (такие как массив), можно обойти коллекцию индексно. Главный секрет — двигаться не с начала таблицы к концу, а с конца к началу (снизу вверх).
Почему это работает? Когда мы удаляем, например, строку с индексом 5, строки с индексами от 0 до 4 остаются на своих местах. Их индексы не меняются, следовательно, наш счетчик, идущий на уменьшение, всегда будет указывать на корректные и еще не обработанные элементы.
Проанализируем пример реализации через цикл Пока:
// Начинаем с самого последнего индекса в табличной части
Индекс = Объект.Товары.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТоваров = Объект.Товары[Индекс];
Если СтрокаТоваров.Номенклатура.Код = "00-00005130"
Или СтрокаТоваров.Номенклатура.Код = "ЦБ-00005141" Тогда
// Удаление по индексу происходит быстрее, чем по объекту строки
Объект.Товары.Удалить(Индекс);
КонецЕсли;
// Переходим к предыдущей строке
Индекс = Индекс - 1;
КонецЦикла;
Важное предупреждение: Никогда не пытайтесь реализовать этот алгоритм через классический цикл Для Индекс = 0 По Количество - 1 с ручным уменьшением переменной-счетчика внутри цикла (как предлагали некоторые участники в сообщении 9). В платформе 1С верхняя граница цикла Для вычисляется ровно один раз при его инициализации. Даже если вы будете вручную вычитать единицу из счетчика, цикл все равно выполнит первоначально запланированное число итераций, что неизбежно приведет к ошибке «Индекс находится за пределами границы» на последних шагах.
Если условием для удаления строк является точное совпадение конкретных реквизитов (например, равенство определенной номенклатуре, складу или контрагенту), то ручной обход в цикле — это не лучшее решение с точки зрения производительности. Платформа 1С предоставляет высокоэффективный встроенный метод НайтиСтроки(), работающий на уровне СУБД/внутренних алгоритмов платформы в разы быстрее обычных циклов.
Рассмотрим, как правильно использовать этот метод для нашей задачи:
// Получаем ссылки на элементы номенклатуры один раз до цикла (оптимизация запросов)
Номенклатура1 = Справочники.Номенклатура.НайтиПоКоду("00-00005130");
Номенклатура2 = Справочники.Номенклатура.НайтиПоКоду("ЦБ-00005141");
// Шаг 1: Удаляем строки с первой номенклатурой
ПараметрыОтбора = Новый Структура("Номенклатура", Номенклатура1);
НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыОтбора);
Для Каждого Строка Из НайденныеСтроки Цикл
Объект.Товары.Удалить(Строка);
КонецЦикла;
// Шаг 2: Удаляем строки со второй номенклатурой
ПараметрыОтбора.Вставить("Номенклатура", Номенклатура2);
НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыОтбора);
Для Каждого Строка Из НайденныеСтроки Цикл
Объект.Товары.Удалить(Строка);
КонецЦикла;
Этот подход не только лаконичен, но и показывает превосходное быстродействие на больших табличных частях (от 1000 строк и более).
В процессе обсуждения на форумах часто всплывают устаревшие или откровенно вредные советы. Выясним, какие подходы использовать категорически нельзя и почему:
В Сообщении 23 пользователю предлагают выгрузить ТЧ в Таблицу значений, произвести удаление там, а затем загрузить обратно с помощью метода Объект.Товары.Загрузить(Товары).
Почему это опасно: В типовых конфигурациях (таких как Управление торговлей 11, ERP 2, Комплексная автоматизация) строки табличной части связаны уникальными идентификаторами (реквизиты КлючСвязи, КлючСтроки) с другими подчиненными табличными частями (например, этапами оплаты, скидками, сериями номенклатуры или акцизами). Метод Загрузить() полностью перезаписывает строки, генерируя новые внутренние идентификаторы. Это гарантированно приведет к потере связей между таблицами, невозможности провести документ и критическому искажению данных.
Обратите внимание на конструкцию СтрокаТоваров.Номенклатура.Код. Когда вы обращаетесь к реквизиту через точку от ссылочного поля внутри цикла, платформа выполняет отдельный неявный запрос к базе данных для каждой строки табличной части. Если в документе 500 строк, система сделает 500 микрозапросов к СУБД, что вызовет сильное зависание системы.
Как правильно: Сначала найдите нужные элементы номенклатуры (по коду, артикулу или наименованию) и запишите их ссылки в переменные, а в цикле сравнивайте уже сами ссылки: СтрокаТоваров.Номенклатура = СсылкаНаНужныйТовар.
Для качественной и быстрой разработки в 1С придерживайтесь простых правил:
НайтиСтроки().Пока с уменьшением индекса.Выгрузить() и Загрузить() для изменения состава строк документов в современных ERP-системах.