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