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