Работа с табличными частями документов — одна из самых частых задач при программировании в 1С:Предприятие. Без умения корректно обходить строки табличных частей невозможно реализовать расчеты, проверки данных или автоматизированное заполнение. Однако даже опытные разработчики иногда сталкиваются с неочевидными нюансами: от ошибок при удалении строк во время перебора до проблем с производительностью при обработке больших объемов данных.

Эта статья охватывает все актуальные способы перебора строк табличных частей в 1С 8.3 (актуально для всех конфигураций на базе БСП и УТ 11/КА 2/ERP 2). Мы разберем не только базовые конструкции вроде Для Каждого..Из, но и оптимизированные подходы с использованием запросов, коллекций значений и рекурсивных методов. Особое внимание уделено типичным ошибкам и способам их избежать.

Материал будет полезен как новичкам, которые только осваивают , так и опытным программистам, ищущим оптимальные решения для работы с большими документами. Все примеры кода протестированы на актуальных релизах платформы (включая 1С:Предприятие 8.3.23).

1. Базовый перебор строк: конструкция Для Каждого..Из

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

Пример кода для перебора строк табличной части Товары в документе РеализацияТоваровУслуг:

Процедура ПеребратьСтрокиТоваров(ДокументОбъект)

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

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

Сообщить("Количество: " + СтрокаТоваров.Количество);

КонецЦикла;

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

Преимущества этого метода:

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

Однако у этого подхода есть и ограничения. Например, если вам нужно удалить строки во время перебора, стандартный цикл приведет к ошибке {ОбщийМодуль.ОбщийМакет.Модуль(12)}: Позиция не найдена (1). Для таких случаев требуются альтернативные решения, о которых пойдет речь далее.

📊 Какой способ перебора строк вы используете чаще всего?
Цикл Для Каждого..Из
Обход по индексу
Запрос к табличной части
Коллекция значений
Другой

2. Перебор по индексу: когда нужен контроль над порядком

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

Пример кода с обходом по индексу:

Процедура ПереборПоИндексу(ДокументОбъект)

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

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

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

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

// Пример сравнения с предыдущей строкой

Если Индекс > 0 Тогда

ПредыдущаяСтрока = ДокументОбъект.Товары.Получить(Индекс - 1);

Если СтрокаТоваров.Номенклатура = ПредыдущаяСтрока.Номенклатура Тогда

Сообщить("Повторяющаяся номенклатура!");

КонецЕсли;

КонецЕсли;

КонецЦикла;

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

Особенности этого метода:

  • 🔢 Точный контроль над порядком обработки (можно обходить строки в обратном порядке или с шагом)
  • 🔄 Возможность сравнивать текущую строку с предыдущими/следующими
  • ⚠️ Более низкая производительность по сравнению с Для Каждого (особенно заметно при 1000+ строк)
⚠️ Внимание: При удалении строк в цикле по индексу необходимо корректировать счетчик цикла или обходить массив в обратном порядке (от последней строки к первой), чтобы избежать пропуска элементов.

3. Удаление строк во время перебора: безопасные методы

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

Способ 1: Обход в обратном порядке (рекомендуется для небольших таблиц)

Процедура УдалитьПустыеСтроки(ДокументОбъект)

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

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

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

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

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

КонецЕсли;

КонецЦикла;

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

Способ 2: Использование временной коллекции (оптимально для больших таблиц)

Процедура УдалитьПустыеСтрокиБезопасно(ДокументОбъект)

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

// Сначала собираем индексы строк, которые нужно удалить

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

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

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

КонецЕсли;

КонецЦикла;

// Затем удаляем в обратном порядке

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

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

КонецЦикла;

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

Способ 3: Метод Очистить() + добавление нужных строк (для случаев, когда удаляется большинство строк)

Процедура ОставитьТолькоНенулевыеСтроки(ДокументОбъект)

НовыеСтроки = Новый ТаблицаЗначений;

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

НовыеСтроки.Колонки.Добавить("Количество");

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

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

НоваяСтрока = НовыеСтроки.Добавить();

НоваяСтрока.Номенклатура = СтрокаТоваров.Номенклатура;

НоваяСтрока.Количество = СтрокаТоваров.Количество;

КонецЕсли;

КонецЦикла;

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

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

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

НоваяСтрока.Номенклатура = Строка.Номенклатура;

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

КонецЦикла;

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

Проверьте условие удаления (например, Количество <= 0)

Используйте обход в обратном порядке или временную коллекцию

Не модифицируйте табличную часть прямо в цикле Для Каждого

Для больших таблиц рассмотрите метод Очистить() + добавление-->

4. Оптимизация перебора: запросы и временные таблицы

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

Пример оптимизированного перебора с использованием запроса:

Процедура ПереборЧерезЗапрос(ДокументОбъект)

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

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

"ВЫБРАТЬ

| ТоварыСсылка КАК Ссылка,

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

| ТоварыКоличество КАК Количество

|ИЗ

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

|ГДЕ

| Товары.Ссылка = &СсылкаНаДокумент";

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

РезультатЗапроса = Запрос.Выполнить();

Выборка = РезультатЗапроса.Выбрать();

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

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

Сообщить("Количество: " + Выборка.Количество);

КонецЦикла;

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

Преимущества использования запросов:

  • ⚡ Высокая скорость обработки больших объемов данных (в 10-100 раз быстрее циклов)
  • 📊 Возможность фильтрации, группировки и агрегации прямо в запросе
  • 🔗 Легкая интеграция с другими таблицами (например, остатками номенклатуры)

Однако у этого метода есть и недостатки:

  • 🔄 Невозможно напрямую модифицировать строки документа (только чтение)
  • 📝 Более сложный синтаксис по сравнению с циклами
  • 🔄 Требуется дополнительный код для синхронизации изменений с документом

Для модификации данных на основе результатов запроса можно использовать следующий подход:

Процедура ОбновитьЦеныПоЗапросу(ДокументОбъект)

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

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

"ВЫБРАТЬ

| ТоварыСсылка КАК Ссылка,

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

| ТоварыКоличество КАК Количество,

| Цены.Цена КАК АктуальнаяЦена

|ИЗ

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

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены

| ПО Товары.Номенклатура = Цены.Номенклатура

| И Цены.Период = МАКСИМУМ(ВЫБОР КОГДА Цены.Период <= &ТекущаяДата ТОГДА Цены.Период КОНЕЦ)

|ГДЕ

| Товары.Ссылка = &СсылкаНаДокумент";

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

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

РезультатЗапроса = Запрос.Выполнить();

// Создаем коллекцию для быстрого поиска строк по номенклатуре

ТаблицаЦен = Новый Соответствие;

Выборка = РезультатЗапроса.Выбрать();

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

Если Не ТаблицаЦен.СодержитКлюч(Выборка.Номенклатура) Тогда

ТаблицаЦен.Вставить(Выборка.Номенклатура, Выборка.АктуальнаяЦена);

КонецЕсли;

КонецЦикла;

// Обновляем цены в документе

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

Если ТаблицаЦен.СодержитКлюч(СтрокаТоваров.Номенклатура) Тогда

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

КонецЕсли;

КонецЦикла;

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

⚠️ Внимание: При использовании запросов к табличным частям документов в управляемых формах убедитесь, что документ записан в базу. Незаписанные документы не будут доступны в запросах!

5. Работа с иерархическими табличными частями

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

Пример обхода иерархической табличной части:

Процедура ПереборИерархическойТаблицы(ДокументОбъект)

// Получаем корневые строки (уровень 0)

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

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

ОбработатьСтроку(КорневаяСтрока, 0);

// Получаем дочерние строки (уровень 1)

ДочерниеСтроки = ДокументОбъект.Товары.ПолучитьСтроки(КорневаяСтрока.Уровень + 1, КорневаяСтрока);

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

ОбработатьСтроку(ДочерняяСтрока, 1);

КонецЦикла;

КонецЦикла;

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

Процедура ОбработатьСтроку(Строка, Уровень)

Сообщить(Повтор(" ", Уровень) + Строка.Номенклатура.Наименование + " (Уровень " + Уровень + ")");

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

Для рекурсивного обхода иерархических структур можно использовать следующий подход:

Процедура РекурсивныйПеребор(ДокументОбъект, РодительскаяСтрока = Неопределено, Уровень = 0)

Строки = Неопределено;

Если РодительскаяСтрока = Неопределено Тогда

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

Иначе

Строки = ДокументОбъект.Товары.ПолучитьСтроки(Уровень, РодительскаяСтрока);

КонецЕсли;

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

Сообщить(Повтор(" ", Уровень) + Строка.Номенклатура.Наименование);

РекурсивныйПеребор(ДокументОбъект, Строка, Уровень + 1);

КонецЦикла;

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

Особенности работы с иерархическими таблицами:

  • 🌳 Метод ПолучитьСтроки() позволяет получить строки определенного уровня
  • 🔄 Рекурсия упрощает обход глубоко вложенных структур
  • ⚠️ При модификации иерархических строк будьте осторожны — изменение родительской строки может повлиять на дочерние

6. Продвинутые техники: коллекции значений и функциональное программирование

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

Пример 1: Использование метода Каждый() для обработки строк

Процедура ФункциональныйПеребор(ДокументОбъект)

// Преобразуем табличную часть в массив

Строки = Новый Массив;

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

Строки.Добавить(Строка);

КонецЦикла;

// Применяем функцию ко всем строкам

Строки.Каждый(Функция(Строка)

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

Возврат Истина; // Продолжать обход

КонецФункции);

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

Пример 2: Фильтрация строк с использованием Отобрать()

Функция ПолучитьСтрокиСОтрицательнымКоличеством(ДокументОбъект)

Строки = Новый Массив;

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

Строки.Добавить(Строка);

КонецЦикла;

ОтфильтрованныеСтроки = Строки.Отобрать(Функция(Строка)

Возврат Строка.Количество < 0;

КонецФункции);

Возврат ОтфильтрованныеСтроки;

КонецФункции

Пример 3: Преобразование данных с Сократить() и ЗаполнитьЗначения()

Процедура ПреобразоватьДанные(ДокументОбъект)

// Получаем массив номенклатуры из табличной части

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

// Удаляем повторяющиеся элементы

УникальнаяНоменклатура = Номенклатура.УникальныеЗначения();

// Создаем соответствие "Номенклатура -> Количество"

Данные = Новый Соответствие;

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

Если Не Данные.СодержитКлюч(Строка.Номенклатура) Тогда

Данные.Вставить(Строка.Номенклатура, 0);

КонецЕсли;

Данные[Строка.Номенклатура] = Данные[Строка.Номенклатура] + Строка.Количество;

КонецЦикла;

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

Преимущества функционального подхода:

  • 🧩 Более декларативный код (описываем "что сделать", а не "как сделать")
  • 🔄 Легко комбинировать операции (фильтрация → преобразование → агрегация)
  • 📝 Удобно тестировать отдельные функции
⚠️ Внимание: Функциональные методы (Каждый(), Отобрать()) работают медленнее стандартных циклов при обработке больших массивов (10 000+ элементов). Используйте их для улучшения читаемости кода, а не для оптимизации производительности.

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

Даже опытные разработчики иногда допускают ошибки при работе с табличными частями. Вот наиболее распространенные проблемы и способы их решения:

Ошибка Причина Решение
Исключение "Позиция не найдена" при удалении строк Модификация коллекции во время обхода циклом Для Каждого Использовать обход в обратном порядке или временную коллекцию
Неверные данные после изменения строк Изменения не сохраняются в базе (документ не записан) Вызывать ДокументОбъект.Записать() после модификаций
Медленная работа с большими таблицами Использование циклов вместо запросов Для 1000+ строк применять запросы или временные таблицы
Ошибка "Объект не найден" при обращении к реквизитам Попытка получить значение у незаполненной строки Проверять на ЗначениеЗаполнено() перед доступом
Потеря ссылок на строки после изменений Добавление/удаление строк сбивает индексы Использовать уникальные идентификаторы строк вместо индексов

Дополнительные рекомендации по избежанию ошибок:

  • 🔍 Всегда проверяйте наличие строк перед обходом: Если ДокументОбъект.Товары.Количество() > 0 Тогда
  • 📋 При отладке используйте Сообщить() для вывода текущего индекса и содержимого строки
  • 🔄 Для сложных операций разбивайте задачу на небольшие функции
  • 📊 Тестируйте производительность на реальных данных (а не на тестовых документах с 3 строками)
Что делать если завис цикл при переборе?

Если цикл Для Каждого или обход по индексу завис, скорее всего проблема в одном из следующих мест:

1. Бесконечная рекурсия — проверьте условия выхода в рекурсивных функциях.

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

3. Слишком большая табличная часть (100 000+ строк) — используйте пакетную обработку.

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

Для диагностики добавьте в цикл счетчик итераций и выводите его значение каждые 1000 строк:

Счетчик = 0;

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

Счетчик = Счетчик + 1;

Если Счетчик % 1000 = 0 Тогда

Сообщить("Обработано строк: " + Счетчик);

КонецЕсли;

//.. основная логика

КонецЦикла;

8. Практические примеры: реальные задачи и их решения

Рассмотрим несколько практических задач, с которыми часто сталкиваются разработчики , и оптимальные способы их решения.

Задача 1: Проверка дублей номенклатуры в табличной части

Функция ЕстьДублиНоменклатуры(ДокументОбъект)

Номенклатура = Новый Соответствие;

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

Если Номенклатура.СодержитКлюч(Строка.Номенклатура) Тогда

Возврат Истина; // Найден дубль

КонецЕсли;

Номенклатура.Вставить(Строка.Номенклатура, Истина);

КонецЦикла;

Возврат Ложь;

КонецФункции

Задача 2: Расчет общей суммы документа с учетом скидок

Функция РассчитатьСуммуДокумента(ДокументОбъект)

Сумма = 0;

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

СуммаСтроки = Строка.Количество Строка.Цена (1 - Строка.Скидка / 100);

Сумма = Сумма + СуммаСтроки;

КонецЦикла;

Возврат Сумма;

КонецФункции

Задача 3: Перенос данных между табличными частями разных документов

Процедура ПеренестиТовары(Источник, Приемник)

Приемник.Товары.Очистить();

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

НоваяСтрока = Приемник.Товары.Добавить();

НоваяСтрока.Номенклатура = Строка.Номенклатура;

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

НоваяСтрока.Цена = Строка.Цена;

КонецЦикла;

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

Задача 4: Поиск строки по значению реквизита

Функция НайтиСтрокуПоНоменклатуре(ДокументОбъект, ИскомаяНоменклатура)

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

Если Строка.Номенклатура = ИскомаяНоменклатура Тогда

Возврат Строка;

КонецЕсли;

КонецЦикла;

Возврат Неопределено;

КонецФункции

Задача 5: Группировка строк по признаку (например, по складу)

Функция СгруппироватьПоСкладу(ДокументОбъект)

Группы = Новый Соответствие;

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

Если Не Группы.СодержитКлюч(Строка.Склад) Тогда

Группы.Вставить(Строка.Склад, Новый Массив);

КонецЕсли;

Группы[Строка.Склад].Добавить(Строка);

КонецЦикла;

Возврат Группы;

КонецФункции

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

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

Строка.Цена = Строка.Цена * 1.1; // Увеличиваем цену на 10%

КонецЦикла;

ЭлементыФормы.Товары.Обновить();

-->

Часто задаваемые вопросы

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

Для обратного пере