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