Работа с табличными частями документов — одна из самых частых задач при программировании в 1С:Предприятие. Без умения корректно обходить строки табличных частей невозможно реализовать расчеты, проверки данных или автоматизированное заполнение. Однако даже опытные разработчики иногда сталкиваются с неочевидными нюансами: от ошибок при удалении строк во время перебора до проблем с производительностью при обработке больших объемов данных.
Эта статья охватывает все актуальные способы перебора строк табличных частей в 1С 8.3 (актуально для всех конфигураций на базе БСП и УТ 11/КА 2/ERP 2). Мы разберем не только базовые конструкции вроде Для Каждого..Из, но и оптимизированные подходы с использованием запросов, коллекций значений и рекурсивных методов. Особое внимание уделено типичным ошибкам и способам их избежать.
Материал будет полезен как новичкам, которые только осваивают 1С, так и опытным программистам, ищущим оптимальные решения для работы с большими документами. Все примеры кода протестированы на актуальных релизах платформы (включая 1С:Предприятие 8.3.23).
1. Базовый перебор строк: конструкция Для Каждого..Из
Самый распространенный и интуитивно понятный способ обхода строк табличной части — использование цикла Для Каждого..Из. Этот метод подходит для большинства задач, где требуется последовательно обработать каждую строку без сложных условий.
Пример кода для перебора строк табличной части Товары в документе РеализацияТоваровУслуг:
Процедура ПеребратьСтрокиТоваров(ДокументОбъект)
Для Каждого СтрокаТоваров Из ДокументОбъект.Товары Цикл
Сообщить("Номенклатура: " + СтрокаТоваров.Номенклатура.Наименование);
Сообщить("Количество: " + СтрокаТоваров.Количество);
КонецЦикла;
КонецПроцедуры
Преимущества этого метода:
- 🔹 Простота и читаемость кода — идеально для начинающих
- 🔹 Автоматическое определение типа данных (
СтрокаТоваровбудет иметь типДокументОбъект.Товары.Строка) - 🔹 Возможность модификации строк прямо в цикле (изменение значений полей)
Однако у этого подхода есть и ограничения. Например, если вам нужно удалить строки во время перебора, стандартный цикл приведет к ошибке {ОбщийМодуль.ОбщийМакет.Модуль(12)}: Позиция не найдена (1). Для таких случаев требуются альтернативные решения, о которых пойдет речь далее.
2. Перебор по индексу: когда нужен контроль над порядком
В некоторых сценариях требуется явный контроль над порядком обхода строк или доступ к строке по ее номеру. Например, при реализации алгоритмов сортировки или когда необходимо сравнивать соседние строки. В таких случаях удобнее использовать перебор по индексу через свойство Количество() и метод Получить().
Пример кода с обходом по индексу:
Процедура ПереборПоИндексу(ДокументОбъект)
КоличествоСтрок = ДокументОбъект.Товары.Количество();
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
СтрокаТоваров = ДокументОбъект.Товары.Получить(Индекс);
Сообщить("Строка №" + (Индекс + 1) + ": " + СтрокаТоваров.Номенклатура.Наименование);
// Пример сравнения с предыдущей строкой
Если Индекс > 0 Тогда
ПредыдущаяСтрока = ДокументОбъект.Товары.Получить(Индекс - 1);
Если СтрокаТоваров.Номенклатура = ПредыдущаяСтрока.Номенклатура Тогда
Сообщить("Повторяющаяся номенклатура!");
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Особенности этого метода:
- 🔢 Точный контроль над порядком обработки (можно обходить строки в обратном порядке или с шагом)
- 🔄 Возможность сравнивать текущую строку с предыдущими/следующими
- ⚠️ Более низкая производительность по сравнению с
Для Каждого(особенно заметно при 1000+ строк)
⚠️ Внимание: При удалении строк в цикле по индексу необходимо корректировать счетчик цикла или обходить массив в обратном порядке (от последней строки к первой), чтобы избежать пропуска элементов.
3. Удаление строк во время перебора: безопасные методы
Одна из самых распространенных ошибок при работе с табличными частями — попытка удалить строку прямо в цикле Для Каждого. Платформа 1С не позволяет модифицировать коллекцию во время ее обхода, что приводит к исключениям. Решить эту проблему можно несколькими способами.
Способ 1: Обход в обратном порядке (рекомендуется для небольших таблиц)
Процедура УдалитьПустыеСтроки(ДокументОбъект)
КоличествоСтрок = ДокументОбъект.Товары.Количество();
Для Индекс = КоличествоСтрок - 1 По 0 Шаг -1 Цикл
СтрокаТоваров = ДокументОбъект.Товары.Получить(Индекс);
Если СтрокаТоваров.Количество <= 0 Тогда
ДокументОбъект.Товары.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Способ 2: Использование временной коллекции (оптимально для больших таблиц)
Процедура УдалитьПустыеСтрокиБезопасно(ДокументОбъект)
СтрокиКУдалению = Новый Массив;
// Сначала собираем индексы строк, которые нужно удалить
Для Каждого СтрокаТоваров Из ДокументОбъект.Товары Цикл
Если СтрокаТоваров.Количество <= 0 Тогда
СтрокиКУдалению.Добавить(СтрокаТоваров.Индекс);
КонецЕсли;
КонецЦикла;
// Затем удаляем в обратном порядке
Для Каждого Индекс Из СтрокиКУдалению Цикл
ДокументОбъект.Товары.Удалить(Индекс);
КонецЦикла;
КонецПроцедуры
Способ 3: Метод Очистить() + добавление нужных строк (для случаев, когда удаляется большинство строк)
Процедура ОставитьТолькоНенулевыеСтроки(ДокументОбъект)
НовыеСтроки = Новый ТаблицаЗначений;
НовыеСтроки.Колонки.Добавить("Номенклатура");
НовыеСтроки.Колонки.Добавить("Количество");
Для Каждого СтрокаТоваров Из ДокументОбъект.Товары Цикл
Если СтрокаТоваров.Количество > 0 Тогда
НоваяСтрока = НовыеСтроки.Добавить();
НоваяСтрока.Номенклатура = СтрокаТоваров.Номенклатура;
НоваяСтрока.Количество = СтрокаТоваров.Количество;
КонецЕсли;
КонецЦикла;
ДокументОбъект.Товары.Очистить();
Для Каждого Строка Из НовыеСтроки Цикл
НоваяСтрока = ДокументОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = Строка.Номенклатура;
НоваяСтрока.Количество = Строка.Количество;
КонецЦикла;
КонецПроцедуры
Проверьте условие удаления (например, Количество <= 0)
Используйте обход в обратном порядке или временную коллекцию
Не модифицируйте табличную часть прямо в цикле Для Каждого
Для больших таблиц рассмотрите метод Очистить() + добавление-->
4. Оптимизация перебора: запросы и временные таблицы
Когда табличная часть содержит тысячи строк, стандартные циклы могут работать слишком медленно. В таких случаях целесообразно использовать запросы или временные таблицы, которые обрабатывают данные на уровне СУБД, а не в памяти 1С.
Пример оптимизированного перебора с использованием запроса:
Процедура ПереборЧерезЗапрос(ДокументОбъект)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыСсылка КАК Ссылка,
| ТоварыНоменклатура КАК Номенклатура,
| ТоварыКоличество КАК Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &СсылкаНаДокумент";
Запрос.УстановитьПараметр("СсылкаНаДокумент", ДокументОбъект.Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить("Номенклатура: " + Выборка.Номенклатура.Наименование);
Сообщить("Количество: " + Выборка.Количество);
КонецЦикла;
КонецПроцедуры
Преимущества использования запросов:
- ⚡ Высокая скорость обработки больших объемов данных (в 10-100 раз быстрее циклов)
- 📊 Возможность фильтрации, группировки и агрегации прямо в запросе
- 🔗 Легкая интеграция с другими таблицами (например, остатками номенклатуры)
Однако у этого метода есть и недостатки:
- 🔄 Невозможно напрямую модифицировать строки документа (только чтение)
- 📝 Более сложный синтаксис по сравнению с циклами
- 🔄 Требуется дополнительный код для синхронизации изменений с документом
Для модификации данных на основе результатов запроса можно использовать следующий подход:
Процедура ОбновитьЦеныПоЗапросу(ДокументОбъект)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыСсылка КАК Ссылка,
| ТоварыНоменклатура КАК Номенклатура,
| ТоварыКоличество КАК Количество,
| Цены.Цена КАК АктуальнаяЦена
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
| ПО Товары.Номенклатура = Цены.Номенклатура
| И Цены.Период = МАКСИМУМ(ВЫБОР КОГДА Цены.Период <= &ТекущаяДата ТОГДА Цены.Период КОНЕЦ)
|ГДЕ
| Товары.Ссылка = &СсылкаНаДокумент";
Запрос.УстановитьПараметр("СсылкаНаДокумент", ДокументОбъект.Ссылка);
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить();
// Создаем коллекцию для быстрого поиска строк по номенклатуре
ТаблицаЦен = Новый Соответствие;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Если Не ТаблицаЦен.СодержитКлюч(Выборка.Номенклатура) Тогда
ТаблицаЦен.Вставить(Выборка.Номенклатура, Выборка.АктуальнаяЦена);
КонецЕсли;
КонецЦикла;
// Обновляем цены в документе
Для Каждого СтрокаТоваров Из ДокументОбъект.Товары Цикл
Если ТаблицаЦен.СодержитКлюч(СтрокаТоваров.Номенклатура) Тогда
СтрокаТоваров.Цена = ТаблицаЦен.Получить(СтрокаТоваров.Номенклатура);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
⚠️ Внимание: При использовании запросов к табличным частям документов в управляемых формах убедитесь, что документ записан в базу. Незаписанные документы не будут доступны в запросах!
5. Работа с иерархическими табличными частями
Некоторые документы в 1С содержат иерархические табличные части (например, СчетаФактурыВыданные с табличной частью Товары, где строки могут иметь подчиненные строки). Для перебора таких структур требуются специальные подходы.
Пример обхода иерархической табличной части:
Процедура ПереборИерархическойТаблицы(ДокументОбъект)
// Получаем корневые строки (уровень 0)
КорневыеСтроки = ДокументОбъект.Товары.ПолучитьСтроки(0);
Для Каждого КорневаяСтрока Из КорневыеСтроки Цикл
ОбработатьСтроку(КорневаяСтрока, 0);
// Получаем дочерние строки (уровень 1)
ДочерниеСтроки = ДокументОбъект.Товары.ПолучитьСтроки(КорневаяСтрока.Уровень + 1, КорневаяСтрока);
Для Каждого ДочерняяСтрока Из ДочерниеСтроки Цикл
ОбработатьСтроку(ДочерняяСтрока, 1);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Процедура ОбработатьСтроку(Строка, Уровень)
Сообщить(Повтор(" ", Уровень) + Строка.Номенклатура.Наименование + " (Уровень " + Уровень + ")");
КонецПроцедуры
Для рекурсивного обхода иерархических структур можно использовать следующий подход:
Процедура РекурсивныйПеребор(ДокументОбъект, РодительскаяСтрока = Неопределено, Уровень = 0)
Строки = Неопределено;
Если РодительскаяСтрока = Неопределено Тогда
Строки = ДокументОбъект.Товары.ПолучитьСтроки(0);
Иначе
Строки = ДокументОбъект.Товары.ПолучитьСтроки(Уровень, РодительскаяСтрока);
КонецЕсли;
Для Каждого Строка Из Строки Цикл
Сообщить(Повтор(" ", Уровень) + Строка.Номенклатура.Наименование);
РекурсивныйПеребор(ДокументОбъект, Строка, Уровень + 1);
КонецЦикла;
КонецПроцедуры
Особенности работы с иерархическими таблицами:
- 🌳 Метод
ПолучитьСтроки()позволяет получить строки определенного уровня - 🔄 Рекурсия упрощает обход глубоко вложенных структур
- ⚠️ При модификации иерархических строк будьте осторожны — изменение родительской строки может повлиять на дочерние
6. Продвинутые техники: коллекции значений и функциональное программирование
Для опытных разработчиков, стремящихся писать более лаконичный и поддерживаемый код, полезно освоить подходы функционального программирования. В 1С для этого можно использовать коллекции значений (Массив, СписокЗначений, Соответствие) и методы высшего порядка.
Пример 1: Использование метода Каждый() для обработки строк
Процедура ФункциональныйПеребор(ДокументОбъект)
// Преобразуем табличную часть в массив
Строки = Новый Массив;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Строки.Добавить(Строка);
КонецЦикла;
// Применяем функцию ко всем строкам
Строки.Каждый(Функция(Строка)
Сообщить(Строка.Номенклатура.Наименование);
Возврат Истина; // Продолжать обход
КонецФункции);
КонецПроцедуры
Пример 2: Фильтрация строк с использованием Отобрать()
Функция ПолучитьСтрокиСОтрицательнымКоличеством(ДокументОбъект)
Строки = Новый Массив;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Строки.Добавить(Строка);
КонецЦикла;
ОтфильтрованныеСтроки = Строки.Отобрать(Функция(Строка)
Возврат Строка.Количество < 0;
КонецФункции);
Возврат ОтфильтрованныеСтроки;
КонецФункции
Пример 3: Преобразование данных с Сократить() и ЗаполнитьЗначения()
Процедура ПреобразоватьДанные(ДокументОбъект)
// Получаем массив номенклатуры из табличной части
Номенклатура = ДокументОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
// Удаляем повторяющиеся элементы
УникальнаяНоменклатура = Номенклатура.УникальныеЗначения();
// Создаем соответствие "Номенклатура -> Количество"
Данные = Новый Соответствие;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Если Не Данные.СодержитКлюч(Строка.Номенклатура) Тогда
Данные.Вставить(Строка.Номенклатура, 0);
КонецЕсли;
Данные[Строка.Номенклатура] = Данные[Строка.Номенклатура] + Строка.Количество;
КонецЦикла;
КонецПроцедуры
Преимущества функционального подхода:
- 🧩 Более декларативный код (описываем "что сделать", а не "как сделать")
- 🔄 Легко комбинировать операции (фильтрация → преобразование → агрегация)
- 📝 Удобно тестировать отдельные функции
⚠️ Внимание: Функциональные методы (Каждый(),Отобрать()) работают медленнее стандартных циклов при обработке больших массивов (10 000+ элементов). Используйте их для улучшения читаемости кода, а не для оптимизации производительности.
7. Типичные ошибки и как их избежать
Даже опытные разработчики иногда допускают ошибки при работе с табличными частями. Вот наиболее распространенные проблемы и способы их решения:
| Ошибка | Причина | Решение |
|---|---|---|
| Исключение "Позиция не найдена" при удалении строк | Модификация коллекции во время обхода циклом Для Каждого |
Использовать обход в обратном порядке или временную коллекцию |
| Неверные данные после изменения строк | Изменения не сохраняются в базе (документ не записан) | Вызывать ДокументОбъект.Записать() после модификаций |
| Медленная работа с большими таблицами | Использование циклов вместо запросов | Для 1000+ строк применять запросы или временные таблицы |
| Ошибка "Объект не найден" при обращении к реквизитам | Попытка получить значение у незаполненной строки | Проверять на ЗначениеЗаполнено() перед доступом |
| Потеря ссылок на строки после изменений | Добавление/удаление строк сбивает индексы | Использовать уникальные идентификаторы строк вместо индексов |
Дополнительные рекомендации по избежанию ошибок:
- 🔍 Всегда проверяйте наличие строк перед обходом:
Если ДокументОбъект.Товары.Количество() > 0 Тогда - 📋 При отладке используйте
Сообщить()для вывода текущего индекса и содержимого строки - 🔄 Для сложных операций разбивайте задачу на небольшие функции
- 📊 Тестируйте производительность на реальных данных (а не на тестовых документах с 3 строками)
Что делать если завис цикл при переборе?
Если цикл Для Каждого или обход по индексу завис, скорее всего проблема в одном из следующих мест:
1. Бесконечная рекурсия — проверьте условия выхода в рекурсивных функциях.
2. Блокировка данных — если документ заблокирован другим пользователем, обход может ожидать разблокировки.
3. Слишком большая табличная часть (100 000+ строк) — используйте пакетную обработку.
4. Ошибка в обработчике события — если в цикле вызываются события (например, ПриИзменении), они могут содержать зацикленную логику.
Для диагностики добавьте в цикл счетчик итераций и выводите его значение каждые 1000 строк:
Счетчик = 0;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Счетчик = Счетчик + 1;
Если Счетчик % 1000 = 0 Тогда
Сообщить("Обработано строк: " + Счетчик);
КонецЕсли;
//.. основная логика
КонецЦикла;
8. Практические примеры: реальные задачи и их решения
Рассмотрим несколько практических задач, с которыми часто сталкиваются разработчики 1С, и оптимальные способы их решения.
Задача 1: Проверка дублей номенклатуры в табличной части
Функция ЕстьДублиНоменклатуры(ДокументОбъект)
Номенклатура = Новый Соответствие;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Если Номенклатура.СодержитКлюч(Строка.Номенклатура) Тогда
Возврат Истина; // Найден дубль
КонецЕсли;
Номенклатура.Вставить(Строка.Номенклатура, Истина);
КонецЦикла;
Возврат Ложь;
КонецФункции
Задача 2: Расчет общей суммы документа с учетом скидок
Функция РассчитатьСуммуДокумента(ДокументОбъект)
Сумма = 0;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
СуммаСтроки = Строка.Количество Строка.Цена (1 - Строка.Скидка / 100);
Сумма = Сумма + СуммаСтроки;
КонецЦикла;
Возврат Сумма;
КонецФункции
Задача 3: Перенос данных между табличными частями разных документов
Процедура ПеренестиТовары(Источник, Приемник)
Приемник.Товары.Очистить();
Для Каждого Строка Из Источник.Товары Цикл
НоваяСтрока = Приемник.Товары.Добавить();
НоваяСтрока.Номенклатура = Строка.Номенклатура;
НоваяСтрока.Количество = Строка.Количество;
НоваяСтрока.Цена = Строка.Цена;
КонецЦикла;
КонецПроцедуры
Задача 4: Поиск строки по значению реквизита
Функция НайтиСтрокуПоНоменклатуре(ДокументОбъект, ИскомаяНоменклатура)
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Если Строка.Номенклатура = ИскомаяНоменклатура Тогда
Возврат Строка;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Задача 5: Группировка строк по признаку (например, по складу)
Функция СгруппироватьПоСкладу(ДокументОбъект)
Группы = Новый Соответствие;
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Если Не Группы.СодержитКлюч(Строка.Склад) Тогда
Группы.Вставить(Строка.Склад, Новый Массив);
КонецЕсли;
Группы[Строка.Склад].Добавить(Строка);
КонецЦикла;
Возврат Группы;
КонецФункции
Эти примеры демонстрируют, как гибко можно работать с табличными частями, комбинируя разные подходы в зависимости от задачи.
Для Каждого Строка Из ДокументОбъект.Товары Цикл
Строка.Цена = Строка.Цена * 1.1; // Увеличиваем цену на 10%
КонецЦикла;
ЭлементыФормы.Товары.Обновить();
-->
Часто задаваемые вопросы
Как перебрать строки табличной части в обратном порядке?
Для обратного пере