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

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

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

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

Перебор строк табличной части — одна из самых частых задач при разработке и доработке конфигураций 1С 8. Табличные части есть почти у каждого документа: товары в реализации, начисления в зарплатной ведомости, услуги в акте. Чтобы посчитать итог, изменить цены, проверить заполнение или перенести данные, нужно пройтись по строкам в цикле.

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

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

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

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

Для Каждого СтрокаТовары Из ДокументОбъект.Товары Цикл

СтрокаТовары.Сумма = СтрокаТовары.Количество * СтрокаТовары.Цена;

КонецЦикла;

Здесь ДокументОбъект — объект документа (например, полученный через Документы.РеализацияТоваровУслуг.СоздатьДокумент() или Ссылка.ПолучитьОбъект()), а Товары — имя табличной части. Переменная СтрокаТовары на каждой итерации содержит ссылку на текущую строку, и изменения её реквизитов сразу отражаются в объекте.

Если код выполняется в модуле формы документа, обращение идёт через реквизит формы: Объект.Товары. В модуле объекта табличная часть доступна напрямую по имени — Товары.

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

Перебор по индексу: когда нужен номер строки

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

КоличествоСтрок = ДокументОбъект.Товары.Количество();

Для Индекс = 0 По КоличествоСтрок - 1 Цикл

СтрокаТовары = ДокументОбъект.Товары[Индекс];

Сообщить("Строка " + (Индекс + 1) + ": " + СтрокаТовары.Номенклатура);

КонецЦикла;

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

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

Удаление строк во время перебора

Удалять элементы коллекции внутри цикла Для Каждого нельзя — это приводит к пропуску строк или ошибке, потому что коллекция меняется прямо во время итерации. Правильный приём — обратный индексный цикл от последней строки к первой.

КоличествоСтрок = ДокументОбъект.Товары.Количество();

Для Индекс = -КоличествоСтрок + 1 По 0 Цикл

ТекущийИндекс = -Индекс;

Если ДокументОбъект.Товары[ТекущийИндекс].Количество = 0 Тогда

ДокументОбъект.Товары.Удалить(ТекущийИндекс);

КонецЕсли;

КонецЦикла;

Альтернатива — сначала собрать строки-кандидаты на удаление в массив, а после цикла удалить их методом Удалить(СтрокаТабличнойЧасти). Такой вариант читается проще и безопаснее:

СтрокиКУдалению = Новый Массив;

Для Каждого СтрокаТовары Из ДокументОбъект.Товары Цикл

Если СтрокаТовары.Количество = 0 Тогда

СтрокиКУдалению.Добавить(СтрокаТовары);

КонецЕсли;

КонецЦикла;

Для Каждого Строка Из СтрокиКУдалению Цикл

ДокументОбъект.Товары.Удалить(Строка);

КонецЦикла;

Перебор через Выгрузить() и таблицу значений

Метод Выгрузить() копирует табличную часть в таблицу значений. Это полезно, когда нужно сортировать строки, сворачивать их с группировкой, искать по нескольким условиям или передавать данные в другую процедуру, не привязываясь к объекту документа.

ТаблицаТоваров = ДокументОбъект.Товары.Выгрузить();

ТаблицаТоваров.Сортировать("Номенклатура");

Для Каждого СтрокаТЗ Из ТаблицаТоваров Цикл

// обработка отсортированных строк

КонецЦикла;

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

Метод Выгрузить() недоступен в некоторых контекстах тонкого и веб-клиента на управляемой форме без вызова сервера. В клиент-серверном коде выгрузку и загрузку выполняйте в серверных процедурах с директивой &НаСервере.

Перебор строк на управляемой форме

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

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

&НаСервере

Процедура ПересчитатьЦеныНаСервере()

Для Каждого СтрокаТовары Из Объект.Товары Цикл

СтрокаТовары.Цена = ПолучитьЦену(СтрокаТовары.Номенклатура, Объект.ТипЦен);

СтрокаТовары.Сумма = СтрокаТовары.Количество * СтрокаТовары.Цена;

КонецЦикла;

КонецПроцедуры

Для обработки только выделенных пользователем строк используйте свойство ВыделенныеСтроки элемента формы табличной части — оно возвращает массив идентификаторов, по которым строку получают методом НайтиПоИдентификатору().

Порядок действий при пересчёте строк на форме
  1. 1
    Определите, нужны ли в цикле данные с сервера (справочники, остатки)
  2. 2
    Если да — создайте серверную процедуру и перенесите цикл туда
  3. 3
    В цикле обращайтесь к строкам через Объект.ИмяТабличнойЧасти
  4. 4
    После цикла при необходимости установите признак модифицированности
  5. 5
    Проверьте результат на документе с несколькими строками и на пустой табличной части

Отбор строк и поиск без полного перебора

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

Отбор = Новый Структура("Номенклатура", ИскомаяНоменклатура);

НайденныеСтроки = ДокументОбъект.Товары.НайтиСтроки(Отбор);

Для Каждого Строка Из НайденныеСтроки Цикл

Строка.Количество = Строка.Количество + 1;

КонецЦикла;

Для подсчёта итога по числовой колонке без цикла есть метод Итог("Сумма"). А если нужно сгруппировать строки и подсчитать суммы по группам — выгрузите табличную часть и примените Свернуть() к таблице значений.

Сравнение способов обхода строк

Для Каждого ... Цикл

Универсальный читаемый вариант для чтения и изменения строк; не подходит для удаления внутри цикла

Индексный цикл

Нужен при удалении строк (обратный обход) и когда важен номер строки; код менее наглядный

Выгрузить() + таблица значений

Для сортировки, свёртки, сложных отборов; требует Загрузить() для возврата данных и памяти на копию

НайтиСтроки()

Быстрый поиск по равенству значений; не поддерживает сложные условия вида «больше/меньше»

Типичные ошибки и как их избежать

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

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

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

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

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

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

Совпадает с ожидаемым: не изменилось при пересчёте, уменьшилось на число удалённых

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

Строк меньше, чем планировалось, — вероятно, удаление шло внутри цикла Для Каждого

Итоговые суммы документа
Норма

Пересчитанные суммы сходятся с контрольным расчётом вручную по нескольким строкам

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

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

Проведение документа после изменений
Норма

Документ записывается и проводится без ошибок

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

Ошибки при записи — нарушены обязательные реквизиты строк после изменений

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

Как перебрать строки документа, не открывая его объект?

Через запрос к табличной части: «ВЫБРАТЬ ... ИЗ Документ.РеализацияТоваровУслуг.Товары ГДЕ Ссылка = &Ссылка». Результат обходят выборкой запроса. Это быстрее, чем ПолучитьОбъект(), и подходит для чтения, но не для изменения данных.

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

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

Можно ли прервать цикл при выполнении условия?

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

Почему изменения строк не сохраняются после цикла?

Чаще всего объект не записан после изменений — вызовите ДокументОбъект.Записать(). Вторая причина — изменялась выгруженная таблица значений без Загрузить() обратно в табличную часть.

Как перебрать строки нескольких табличных частей одного документа?

Обратитесь к метаданным: Для Каждого ТабЧасть Из ДокументОбъект.Метаданные().ТабличныеЧасти Цикл, затем обходите ДокументОбъект[ТабЧасть.Имя]. Это удобно для универсальных обработок копирования или очистки документов.

Что быстрее: цикл по табличной части или запрос?

Для чтения большого числа документов запрос почти всегда эффективнее, так как не создаёт объекты в памяти. Для изменения строк одного документа цикл по объекту — стандартный и безопасный способ.

Итог

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

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