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

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

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

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

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

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

Обход табличной части объекта документа через «Для Каждого»

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

Для Каждого СтрокаТЧ Из Товары Цикл

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

Если СтрокаТЧ.Сумма <> СуммаСтроки Тогда

СтрокаТЧ.Сумма = СуммаСтроки;

КонецЕсли;

КонецЦикла;

Внутри цикла переменная СтрокаТЧ — это объект строки табличной части, все изменения в нём сразу отражаются на данных документа. Это удобно для пересчёта сумм, подстановки значений по умолчанию, проверки заполнения реквизитов перед записью.

Если нужно прервать обход при первом совпадении, используйте Прервать, а для пропуска отдельных строк — Продолжить. Это стандартные операторы встроенного языка, они работают в любом цикле.

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

Цикл по индексу применяют, когда нужен номер строки, обход в обратном порядке или удаление строк прямо во время перебора. Индексация строк начинается с нуля:

Для Индекс = 0 По Товары.Количество() - 1 Цикл

СтрокаТЧ = Товары[Индекс];

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

КонецЦикла;

Для удаления строк обязательно идти с конца, иначе индексы сместятся и часть строк будет пропущена:

Для Индекс = -Товары.Количество() + 1 По 0 Цикл

РеальныйИндекс = -Индекс;

Если Товары[РеальныйИндекс].Количество = 0 Тогда

Товары.Удалить(РеальныйИндекс);

КонецЕсли;

КонецЦикла;

Альтернатива — собрать строки-кандидаты в массив при обычном обходе «Для Каждого», а затем удалить их в отдельном цикле. Такой код проще читать и сложнее сломать.

Типичные ошибки
01
Удаление строк в прямом цикле по индексу — после удаления индексы сдвигаются, строки пропускаются
02
Использование Товары.Количество() в условии цикла при изменении состава — граница цикла вычисляется один раз
03
Обращение к строке по номеру строки вместо индекса — номер начинается с 1, индекс с 0

Обход табличной части через ссылку и запрос

Если у вас есть только ссылка на документ, табличную часть можно прочитать напрямую: Ссылка.Товары вернёт таблицу значений, которую можно обойти тем же циклом. Однако каждое такое обращение — это чтение из базы, и в цикле по многим документам это даёт лишние обращения к СУБД.

Правильный способ для пакетной обработки — запрос. Он получает данные всех документов за одно обращение:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| РеализацияТовары.Номенклатура КАК Номенклатура,

| РеализацияТовары.Количество КАК Количество

|ИЗ

| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары

|ГДЕ

| РеализацияТовары.Ссылка = &Ссылка";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

// обработка строки

КонецЦикла;

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

Сравнение вариантов

Для Каждого по объекту

Подходит для модуля объекта и формы; позволяет менять строки; требует полученного объекта

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

Нужен при удалении строк и обходе с конца; код менее читаемый, легко ошибиться с границами

Запрос к табличной части

Лучший вариант для обработки многих документов по ссылкам; только чтение, недоступен на клиенте

Обращение Ссылка.Товары

Просто для разового чтения одного документа; неприемлемо в цикле по сотням документов из-за нагрузки на базу

Обход табличной части на клиенте в управляемой форме

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

&НаКлиенте

Процедура ПересчитатьСуммы(Команда)

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

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

КонецЦикла;

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

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

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

Порядок действий при переносе обхода на сервер
  1. 1
    Создайте серверную процедуру с директивой &НаСервере
  2. 2
    Передайте в неё ссылку на документ или ключевые параметры, а не всю форму
  3. 3
    Выполните запрос или получите объект и обойдите табличную часть
  4. 4
    Верните результат структурой или таблицей значений и отразите его в форме
  5. 5
    Проверьте, что процедура не вызывается в цикле — иначе получите множество серверных вызовов

Обход с отбором: НайтиСтроки и итоги

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

Отбор = Новый Структура("Склад", СкладОтгрузки);

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

Для Каждого СтрокаТЧ Из СтрокиСклада Цикл

// обработка только строк по нужному складу

КонецЦикла;

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

Если отбор сложный (диапазоны, условия «ИЛИ», сравнение с вычисляемыми значениями), НайтиСтроки не справится — там уместен обычный цикл с проверкой условия внутри или запрос с предикатом ГДЕ.

НайтиСтроки сравнивает значения строго по равенству и с учётом типов. Если в отборе передать строку вместо ссылки, поиск ничего не найдёт — типы должны совпадать с типом колонки табличной части.
ПЗ
Практическое замечание

Проверка результата и ограничения

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

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

Отдельный риск — обход табличной части в обработчике проведения с одновременным изменением самого документа и записью других объектов. Убедитесь, что логика не вызывает повторную запись того же документа, иначе возможна рекурсия или конфликт блокировок.

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

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

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

Код завершается без ошибок, цикл не выполняется

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

Ошибка обращения к строке или деление на ноль после цикла

Удаление строк по условию
Норма

Удалены именно целевые строки, остальные на месте

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

Удалены лишние строки или часть целевых осталась

Производительность на большом объёме
Норма

Обработка документа занимает секунды, один запрос к базе

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

Время растёт линейно от числа строк из-за обращений к базе в цикле

Сохранение изменений
Норма

После Записать() данные в базе соответствуют расчёту

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

Изменения видны в форме, но теряются после закрытия

FAQ

Чем отличается обход табличной части объекта от обхода через ссылку?

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

Как обойти только выделенные пользователем строки в форме?

Переберите коллекцию Элементы.Товары.ВыделенныеСтроки — в ней лежат идентификаторы строк. Каждую строку данных получайте методом Объект.Товары.НайтиПоИдентификатору(Идентификатор) и работайте с ней как обычно.

Можно ли обойти табличную часть запросом на клиенте?

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

Как безопасно удалить строки во время обхода?

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

Что быстрее: цикл с накоплением суммы или метод Итог()?

Метод Итог("ИмяКолонки") быстрее и короче: он реализован на уровне платформы и не требует написания цикла. Ручной обход оправдан только если вместе с суммой нужно выполнить дополнительные действия над строками.

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

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

Итог

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

Базовый приём обхода табличной части — цикл «Для Каждого»; для удаления строк идите с конца, для массовой обработки документов используйте запрос, а на клиенте работайте с ДанныеФормыКоллекции и выносите обращения к базе на сервер.