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