К статье
Практический материал

Разработка и Конфигурация · 10 мин

Как перебрать строки табличной части в 1С

Как перебрать строки табличной части в 1С: цикл Для Каждого, поиск по условию, изменение и удаление строк, работа с выборкой запроса. Примеры кода и ошибки.

Перебор строк табличной части — одна из самых частых задач при доработке конфигураций 1С: заполнение цен, проверка дублей, расчёт итогов, очистка строк по условию. Встроенный язык предлагает несколько способов: классический цикл Для Каждого, цикл со счётчиком, методы НайтиСтроки() и запрос по табличной части. Выбор зависит от того, где выполняется код (клиент или сервер), нужно ли изменять коллекцию в процессе и насколько важна скорость.

Табличная часть объекта (документа, справочника) — это коллекция строк типа ТабличнаяЧасть, каждая строка имеет индекс, номер строки и набор реквизитов. Обращение к ней выглядит как Объект.Товары или ТабличнаяЧасть внутри модуля объекта. Для таблицы значений, загруженной на форму как реквизит, синтаксис перебора тот же, но есть нюансы с типом ДанныеФормыКоллекция.

Ниже разобраны основные приёмы с примерами кода, ограничения каждого способа, типичные ошибки (например, удаление строк внутри прямого цикла) и способы проверить результат. Примеры приведены для управляемых форм и обычного приложения — синтаксис перебора идентичен.

Базовый перебор циклом «Для Каждого»

Самый читаемый и безопасный способ — цикл Для Каждого. На каждой итерации переменная получает ссылку на текущую строку, и через неё можно читать и менять реквизиты строки напрямую. Изменения сохраняются в объекте, дополнительно присваивать строку обратно не нужно.

bsl
Для Каждого СтрокаТовары Из Объект.Товары Цикл
	Если СтрокаТовары.Количество = 0 Тогда
		СтрокаТовары.Сумма = 0;
	Иначе
		СтрокаТовары.Сумма = СтрокаТовары.Количество * СтрокаТовары.Цена;
	КонецЕсли;
КонецЦикла;

Цикл подходит для чтения и изменения реквизитов существующих строк. Ограничение: внутри такого цикла нельзя безопасно удалять текущую строку или добавлять новые в ту же коллекцию — это приводит к пропуску строк или зацикливанию, потому что коллекция меняется «под ногами» у итератора.

Если внутри модуля объекта документа, обращение упрощается до Для Каждого Строка Из Товары Цикл, поскольку табличная часть доступна напрямую по имени. В модуле формы, где данные представлены как ДанныеФормыКоллекция, синтаксис тот же: Для Каждого Строка Из Объект.Товары Цикл.

Перебор по индексу и обход с конца

Цикл со счётчиком нужен, когда важен номер строки, порядок обхода или когда коллекцию требуется изменять в процессе. Строки табличной части индексируются с нуля, а метод Количество() возвращает число строк.

bsl
Для Индекс = 0 По Объект.Товары.Количество() - 1 Цикл
	СтрокаТовары = Объект.Товары[Индекс];
	СтрокаТовары.НомерПоПорядку = Индекс + 1;
КонецЦикла;

// Удаление строк — только с конца:
Для Сч = 1 По Объект.Товары.Количество() Цикл
	Индекс = Объект.Товары.Количество() - Сч;
	Если Объект.Товары[Индекс].Количество = 0 Тогда
		Объект.Товары.Удалить(Индекс);
	КонецЕсли;
КонецЦикла;

Обход с конца — обязательный приём при удалении строк по условию. Если удалять в прямом порядке, после удаления строки с индексом N следующая строка сдвинется на её место, а счётчик уйдёт дальше — строка будет пропущена. Обратный обход устраняет проблему, так как удаление влияет только на уже пройденные индексы.

Альтернатива для массовой очистки — собрать строки-кандидаты в отдельный массив через НайтиСтроки() и затем удалить их в отдельном цикле по массиву. Это нагляднее и меньше риск ошибиться со счётчиком.

Поиск строк по условию: НайтиСтроки и Найти

Когда нужны не все строки, а только соответствующие отбору, полный перебор с проверкой внутри неэффективен. Метод НайтиСтроки(ПараметрыОтбора) принимает структуру с парами «имя реквизита — значение» и возвращает массив найденных строк. Метод Найти(Значение, ИмяКолонки) возвращает первую подходящую строку или Неопределено.

bsl
Отбор = Новый Структура("Номенклатура, Отменено", ТекНоменклатура, Ложь);
НайденныеСтроки = Объект.Товары.НайтиСтроки(Отбор);
Для Каждого СтрокаТовары Из НайденныеСтроки Цикл
	СтрокаТовары.Цена = НоваяЦена;
КонецЦикла;

Если НайденныеСтроки.Количество() = 0 Тогда
	// строк с таким товаром нет — добавляем
КонецЕсли;

Отбор в НайтиСтроки работает только на равенство. Условия вида «больше», «содержит», «не равно» через него не выразить — здесь остаётся полный перебор с проверкой в цикле или запрос. Также учитывайте, что сравнение идёт с учётом типа: число 0 не равно пустой ссылке, а строка сравнивается посимвольно.

Перебор через запрос для больших объёмов

Если строк много и нужны только вычисления, агрегация или сложный отбор, быстрее передать табличную часть в запрос как параметр-таблицу или обратиться к ней напрямую в запросе к документу. Запрос выполняется на сервере СУБД и не требует построчного обхода в коде.

bsl
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Товары.Номенклатура КАК Номенклатура,
|	СУММА(Товары.Количество) КАК Количество
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
|	Товары.Ссылка = &Ссылка
|СГРУППИРОВАТЬ ПО
|	Товары.Номенклатура";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	// обработка сгруппированных данных
КонецЦикла;

Выборка запроса перебирается методом Следующий() в цикле Пока. Важное отличие: строки выборки доступны только для чтения. Если нужно изменить документ, сначала получают объект через Ссылка.ПолучитьОбъект(), затем работают с его табличной частью.

Запрос к табличной части по ссылке не требует чтения всего объекта в память — это существенно быстрее при анализе чужих документов, например в обработках сверки. Но для изменения данных объект всё равно придётся читать целиком.

Сравнение способов перебора

Для Каждого

Чтение и изменение всех строк. Не подходит для удаления и добавления строк в процессе

Цикл по индексу с конца

Удаление строк по условию, обход в заданном порядке. Код менее читаемый, легко ошибиться в границах

НайтиСтроки

Отбор на равенство по одному-нескольким реквизитам. Не поддерживает сравнения и НЕ

Запрос

Агрегация, сложные условия, большие объёмы. Результат только для чтения

Особенности на форме: клиент, сервер и ДанныеФормыКоллекция

В модуле управляемой формы табличная часть объекта доступна как Объект.Товары, и перебор работает одинаково на клиенте и на сервере. Однако тяжёлые циклы (расчёты, обращения к базе) следует выносить в серверные процедуры: на клиенте недоступен запрос и прямое обращение к данным, а вызовы сервера из цикла на каждой строке резко замедлят форму.

Если данные хранятся в реквизите формы типа «ТаблицаЗначений», на клиенте он представлен как ДанныеФормыКоллекция — перебор Для Каждого работает, но методы НайтиСтроки доступны и там. Для передачи на сервер таблицу преобразуют через РеквизитФормыВЗначение() и обратно через ЗначениеВРеквизитФормы().

Ещё одна ловушка — обработчик ПриИзменении поля строки, который сам меняет реквизиты той же строки и вызывает пересчёт, снова triggering обработчик. Чтобы избежать рекурсии, выполняйте взаимные пересчёты через отдельные процедуры с явным контролем, а не через цепочку событий.

Типичные ошибки и как их проверить

Типичные ошибки
01
Удаление строк в прямом цикле Для Каждого — часть строк пропускается, итоговый набор непредсказуем
02
Сравнение реквизита строки с пустым значением без учёта типа: Неопределено, NULL и пустая ссылка — разные значения
03
Обращение к реквизиту, которого нет в табличной части, — ошибка исполнения; имя проверяют по конфигуратору
04
Изменение строк выборки запроса с ожиданием, что документ обновится — выборка только для чтения
05
Вызов сервера внутри клиентского цикла на каждой строке — форма зависает при десятках строк

После доработки кода результат проверяют на тестовой копии базы: открывают документ с разными наборами строк (пустая табличная часть, одна строка, строки с нулевым количеством, дубли номенклатуры) и убеждаются, что обработка отрабатывает корректно на каждом варианте. Пустая табличная часть не должна вызывать ошибку — цикл просто не выполнится ни разу, но код после цикла обязан корректно работать с этим случаем.

Контрольные точки

Как проверить результат

Количество строк после обработки
Норма

Совпадает с ожидаемым для тестового набора

Тревожный признак

Строки пропущены или удалены лишние — признак удаления в прямом цикле

Пересчитанные суммы
Норма

Сумма строки = Количество × Цена с учётом скидок

Тревожный признак

Старые значения — изменения делались в копии или выборке запроса

Пустая табличная часть
Норма

Код завершается без ошибок

Тревожный признак

Ошибка обращения к строке по индексу 0

Полезный приём отладки — временно выводить в цикле Сообщить(Строка(СтрокаТовары.НомерСтроки) + \": \" + СтрокаТовары.Номенклатура), чтобы увидеть фактический порядок и состав обрабатываемых строк. После отладки вывод убирают: сообщения в цикле замедляют выполнение и засоряют окно сообщений пользователя.

Итог

Что важно запомнить

Для чтения и изменения строк используйте Для Каждого, для удаления — обратный цикл по индексу или массив из НайтиСтроки, для агрегации и больших объёмов — запрос. Выборка запроса доступна только для чтения, а изменения строк через Для Каждого сохраняются в объекте сразу.

Частые вопросы

Как узнать номер текущей строки при переборе «Для Каждого»?

У каждой строки табличной части есть свойство НомерСтроки (нумерация с единицы) — его можно читать прямо в цикле: СтрокаТовары.НомерСтроки. Если нужен индекс с нуля, используйте Объект.Товары.Индекс(СтрокаТовары).

Как перебрать строки и добавить новые в ту же табличную часть?

Нельзя добавлять строки в коллекцию, которую перебираете через «Для Каждого», — итератор собьётся. Сначала соберите данные для новых строк во временный массив или таблицу значений, а после завершения цикла добавьте их вторым проходом через Объект.Товары.Добавить().

Почему НайтиСтроки не находит строку, хотя значение совпадает?

Чаще всего не совпадает тип: в отборе число, а в реквизите строка, или сравнивается ссылка с её строковым представлением. Проверьте типы через ТипЗнч() и убедитесь, что передаёте значение того же типа, что хранится в реквизите табличной части.

Как перебрать строки табличной части на управляемой форме на клиенте?

Синтаксис тот же: Для Каждого Строка Из Объект.Товары Цикл работает и в клиентской процедуре. Ограничения клиента: недоступны запросы и прямое чтение данных базы, поэтому обращения к справочникам внутри цикла нужно выносить в один серверный вызов.

Что быстрее — перебор в коде или запрос по табличной части?

Для простого пересчёта нескольких реквизитов разницы практически нет. Запрос выигрывает при группировках, соединениях с другими таблицами и сложных отборах, особенно когда документ не нужно изменять — тогда объект вообще не читается в память.

Как удалить все строки табличной части?

Для полной очистки цикл не нужен — вызовите Объект.Товары.Очистить(). Это быстрее и надёжнее, чем удаление построчно. Циклы с удалением нужны только при выборочной очистке по условию.