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

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

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

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

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

Базовый инструмент — цикл Для Каждого … Из … Цикл, который последовательно возвращает строки коллекции табличной части. Для массовой обработки больших объёмов данных эффективнее запрос к табличной части через параметр-ссылку на документ. Оба подхода рассмотрены ниже с готовыми примерами кода, которые можно адаптировать под любую типовую или самописную конфигурацию.

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

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

Классический способ — обход коллекции строк табличной части в цикле. В модуле объекта документа или в серверной процедуре, где доступен объект документа, код выглядит так:

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

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

КонецЦикла;

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

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

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

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

Перебор с индексом: цикл «Для … По … Цикл»

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

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

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

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

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

КонецЦикла;

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

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

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

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

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

КонецЕсли;

КонецЦикла;

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

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

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

&НаКлиенте

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

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

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

КонецЦикла;

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

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

Для обработки только текущей строки, с которой работает пользователь, используют Элементы.Товары.ТекущиеДанные. Это быстрее и безопаснее, чем перебор всей табличной части, когда действие касается одной позиции — например, при обработчике ПриИзменении поля количества.

Перебор через запрос к табличной части

Когда нужно прочитать строки документа, уже записанного в базу, без загрузки всего объекта в память, оптимален запрос. Табличная часть адресуется через точку от имени документа, а ссылка на конкретный документ передаётся параметром:

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

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

"ВЫБРАТЬ

| Товары.Номенклатура КАК Номенклатура,

| СУММА(Товары.Количество) КАК Количество

|ИЗ

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

|ГДЕ

| Товары.Ссылка = &Ссылка

|СГРУППИРОВАТЬ ПО

| Товары.Номенклатура";

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

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

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

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

КонецЦикла;

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

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

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

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

Подходит для изменения строк и работы с незаписанными данными формы. Требует загруженного объекта, медленнее на больших объёмах.

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

Нужен при удалении строк во время обхода или доступе к соседним строкам. Чуть более громоздкий код.

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

Быстрый для чтения, группировки и соединений по записанным документам. Не позволяет изменять данные.

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

Удобен для сортировки, свёртки и поиска строк методами таблицы значений. Требует дополнительной памяти на копию данных.

Выгрузка табличной части в таблицу значений

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

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

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

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

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

КонецЦикла;

Можно выгрузить не все колонки, а только нужные, передав их список строкой: Объект.Товары.Выгрузить(,"Номенклатура,Количество") — первым параметром указываются отбираемые строки, вторым — колонки. Это снижает потребление памяти на документах с широкими табличными частями.

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

Порядок действий для типовой задачи
  1. 1
    Определите контекст: клиент, сервер формы или модуль объекта — от этого зависит доступный инструментарий.
  2. 2
    Уточните в конфигураторе точное имя табличной части и имена её реквизитов.
  3. 3
    Выберите способ: цикл «Для Каждого» для изменения строк, запрос — для чтения и группировки записанных данных.
  4. 4
    Напишите цикл, обращаясь к реквизитам строки через переменную цикла.
  5. 5
    Проверьте результат на тестовом документе с разным числом строк, включая пустую табличную часть.

Проверка результата и типичные ошибки

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

Частая ошибка — обращение к несуществующему реквизиту строки. Встроенный язык проверяет имена реквизитов только в момент выполнения, поэтому опечатка в имени проявится только при прогоне кода. Перед сдачей доработки выполните синтаксическую проверку модуля и обязательный функциональный прогон на копии базы.

Вторая группа ошибок связана с контекстом исполнения: попытка выполнить запрос на клиенте, вызов серверной процедуры без директивы &НаСервере, обращение к Объект вне модуля формы. Компилятор платформы перехватывает большинство таких ситуаций, поэтому внимательно читайте сообщения проверки модуля.

Третья типичная проблема — изменение коллекции во время её обхода: удаление или вставка строк в прямом цикле «Для Каждого». Симптом — пропущенные строки или исключение «Индекс находится за границами массива». Решение описано выше: обратный обход или промежуточный массив.

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

Как выбрать подход под конкретную задачу

Для интерактивного пересчёта в форме (пользователь нажал кнопку — пересчитались суммы) достаточно клиентского цикла по Объект.Товары, если не требуются данные из базы. Если нужны цены, остатки или курсы — переносите расчёт на сервер.

В обработчике проведения документа перебор обычно делают через запрос с выгрузкой в движения: запрос к табличной части с соединением к справочникам даёт готовый набор данных для записи в регистры. Это стандартный приём типовых конфигураций.

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

Маршрут решения

Как выбрать действие

Нужно изменить строки открытого в форме документаЦикл «Для Каждого» по Объект.ИмяТабличнойЧасти в модуле формы
Нужно прочитать и сгруппировать данные записанного документаЗапрос к табличной части с параметром-ссылкой
Нужно удалить часть строк по условиюИндексный цикл с обходом с конца или сбор строк в массив
Нужна сортировка, свёртка или поиск дублей по строкамВыгрузить() в таблицу значений и работа с ней
Обработка идёт в обработчике проведенияЗапрос к табличной части с соединениями, результат — в движения
Итог

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

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

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

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

Используйте метод Количество(): Если Объект.Товары.Количество() = 0 Тогда … КонецЕсли;. Это позволяет заранее обработать случай пустой табличной части и не запускать лишнюю логику.

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

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

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

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

Почему в клиентской процедуре недоступен запрос к табличной части?

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

Как перебрать строки только с определённым условием, не проверяя каждую в цикле?

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

Влияет ли перебор табличной части на производительность проведения?

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