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

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

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

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

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

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

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

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

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

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

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

КонецЦикла;

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

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

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

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

КонецЦикла;

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

Обход по индексу циклом «Для»

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

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

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

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

КонецЦикла;

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

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

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

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

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

КонецЕсли;

КонецЦикла;

Фильтрация строк без полного перебора

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

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

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

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

СтрокаТовары.Обработано = Истина;

КонецЦикла;

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

Для поиска единственной строки по точному совпадению есть метод Найти(): он возвращает строку или Неопределено, если совпадений нет. Всегда проверяйте результат на Неопределено перед обращением к реквизитам найденной строки.

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

Для Каждого

Основной вариант: читаемый, быстрый. Не подходит для удаления строк во время обхода.

Для по индексу

Нужен для обратного обхода, удаления строк и доступа к соседним строкам. Код громоздче.

НайтиСтроки() + цикл

Когда обрабатывается подмножество строк по равенству реквизитов. Не работает со сложными условиями.

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

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

Итоги и агрегаты без цикла

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

ИтогоСумма = Объект.Товары.Итог("Сумма");

ИтогоКоличество = Объект.Товары.Итог("Количество");

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

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

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

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

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

Если обработка массовая — например, пересчёт всех строк с обращением к ценам из базы — выполняйте её на сервере одним вызовом, а не в клиентском цикле с множественными серверными вызовами. Каждый вызов сервера из цикла — это сетевой обмен, и на табличной части в сотни строк форма заметно «подвисает».

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

Порядок действий при обработке выделенных строк
  1. 1
    Получите массив Элементы.Товары.ВыделенныеСтроки
  2. 2
    Проверьте, что массив не пуст, иначе сообщите пользователю
  3. 3
    В цикле получите строку: Объект.Товары.НайтиПоИдентификатору(Ид)
  4. 4
    Выполните обработку строки и установите Модифицированность = Истина

Типичные ошибки при обходе табличных частей

Первая и самая распространённая ошибка — изменение коллекции во время обхода «Для Каждого». Вторая — путаница между индексом строки (с нуля) и номером строки (с единицы): обращение Товары[НомерСтроки] даст не ту строку или выйдет за границы коллекции.

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

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

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

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

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

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

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

Число обработанных строк
Норма

Совпадает с Количество() или размером массива НайтиСтроки()

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

Счётчик меньше — часть строк пропущена, проверяйте удаление в цикле

Итоги после пересчёта
Норма

Итог("Сумма") сходится с контрольным значением

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

Расхождение — ошибка в формуле пересчёта строки

Запись документа
Норма

Записать() выполняется без исключения

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

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

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

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

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

Чем отличается НомерСтроки от индекса строки?

НомерСтроки — видимый пользователю номер, начинается с 1. Индекс при обращении Товары[Индекс] начинается с 0. Соответствие: индекс = НомерСтроки - 1.

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

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

Можно ли прервать обход досрочно?

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

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

Получите описание табличных частей через метаданные: Для Каждого ТабЧасть Из Метаданные.Документы.РеализацияТоваровУслуг.ТабличныеЧасти Цикл, затем обращайтесь к коллекции по имени: Объект[ТабЧасть.Имя]. Так строят универсальные обработки для любых документов.

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

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

Итог

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

Базовый обход — цикл «Для Каждого Строка Из Объект.Товары Цикл». Удаление строк — только обратным циклом по индексу или после сбора в массив. Фильтрация — НайтиСтроки(), итоги — Итог(), массовые вычисления — на сервере одним вызовом. Помните: индекс с 0, НомерСтроки с 1.