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

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

Как обойти таблицу в 1С: практические способы и примеры

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

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

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

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

Какие «таблицы» бывают в 1С и чем они отличаются

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

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

Обход таблицы значений циклом «Для Каждого»

Базовый и самый читаемый способ — цикл по коллекции строк. Внутри цикла доступны все колонки текущей строки через точку:

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

СуммаДокумента = СуммаДокумента + СтрокаТЗ.Сумма;

КонецЦикла;

Если нужен индекс строки (например, для сообщения пользователю или связи с другой коллекцией), используйте классический цикл со счётчиком. Индексация строк начинается с нуля:

Для Индекс = 0 По ТаблицаЗначений.Количество() - 1 Цикл

СтрокаТЗ = ТаблицаЗначений[Индекс];

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

КонецЦикла;

Цикл «Для Каждого» предпочтительнее, когда индекс не нужен: код короче и меньше риск выйти за границы коллекции. Вариант со счётчиком выбирайте, когда требуется номер строки или обход с шагом.

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

Табличная часть обходится тем же циклом «Для Каждого». Отличие в том, что изменения строк реально меняют объект: после обхода объект нужно записать, иначе правки потеряются.

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

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

СтрокаТЧ.Цена = СтрокаТЧ.Цена * 1.1;

КонецЦикла;

ДокументОбъект.Записать();

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

Обход результата запроса

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

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

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

"ВЫБРАТЬ

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

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

|ИЗ

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

|ГДЕ

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

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

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

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

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

КонецЦикла;

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

Порядок действий
  1. 1
    Определите, с чем работаете: таблица значений, табличная часть или данные базы
  2. 2
    Для данных базы напишите запрос и обойдите выборку через Пока Выборка.Следующий()
  3. 3
    Для таблицы значений или табличной части используйте Для Каждого Строка Из Коллекция Цикл
  4. 4
    Если в цикле нужно удалять строки — соберите их в массив и удаляйте после цикла
  5. 5
    Проверьте результат на тестовой копии базы перед запуском на рабочих данных

Как удалять и изменять строки во время обхода

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

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

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

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

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

КонецЕсли;

КонецЦикла;

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

ТаблицаЗначений.Удалить(Строка);

КонецЦикла;

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

Типичные ошибки
01
Удаление строк внутри цикла Для Каждого — пропускаются соседние строки и результат непредсказуем
02
Выполнение запроса внутри цикла по строкам — резкое падение производительности, запрос нужно вынести из цикла и соединить данные на стороне СУБД
03
Перебор строк табличной части вместо отбора на форме — для динамических списков правильнее менять Отбор, а не обходить строки
04
Забытый Вызов Записать() после изменения табличной части объекта вне формы — изменения теряются молча

Выбор между циклом и запросом: производительность

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

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

Цикл Для Каждого

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

Запрос с выборкой

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

НайтиСтроки с отбором

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

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

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

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

Количество обработанных строк
Норма

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

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

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

Время выполнения обработки
Норма

Растёт примерно линейно с ростом числа строк

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

Растёт резко быстрее линейного — вероятно, внутри цикла есть запросы или поиск по ссылке

Данные после записи
Норма

Изменения видны при повторном открытии объекта

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

Изменения пропали — объект не был записан

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

Чем отличается обход табличной части на форме и в модуле объекта?

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

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

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

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

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

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

При вызове Выбрать() укажите ОбходРезультатаЗапроса.ПоГруппировкам и напишите рекурсивную процедуру: она обходит выборку текущего уровня и для вложенных групп вызывает саму себя с Выборка.Выбрать(). Так обходят деревья любой глубины.

Что быстрее: цикл по таблице значений или запрос?

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

Итог

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

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