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

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

Как перебрать таблицу значений в 1С 8.3

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

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

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

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

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

Таблица значений (объект ТаблицаЗначений) состоит из коллекции колонок (Колонки) и коллекции строк (СтрокаТаблицыЗначений). Каждая строка — это объект с именованными полями, доступ к которым выполняется через точку: Строка.Номенклатура, Строка.Сумма. Имена полей совпадают с именами колонок, заданными при создании таблицы или полученными из запроса.

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

Если ТаблицаТоваров.Количество() = 0 Тогда

Сообщить("Таблица пуста, обработка не требуется");

Возврат;

КонецЕсли;

Ещё один момент — типизация колонок. Если колонка объявлена как число, присвоение строки внутри цикла приведёт либо к преобразованию, либо к ошибке в зависимости от состава типов колонки. Поэтому перед массовым изменением значений полезно посмотреть описание типов колонки через ТаблицаТоваров.Колонки.Сумма.ТипЗначения.

Перебор циклом «Для Каждого» — основной способ

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

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

СтрокаТовара.СуммаСНДС = СтрокаТовара.Сумма * 1.2;

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

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

КонецЕсли;

КонецЦикла;

Внутри цикла доступны управляющие операторы: Прервать — досрочно выйти из цикла, Продолжить — перейти к следующей строке, не выполняя остаток тела цикла. Это удобно для пропуска «неинтересных» строк:

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

Если Не СтрокаТовара.Обрабатывать Тогда

Продолжить;

КонецЕсли;

// основная логика обработки строки

КонецЦикла;

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

Перебор по индексу: цикл «Для» и обратный обход

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

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

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

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

КонецЦикла;

Для удаления строк обход выполняют в обратном порядке — от последней строки к первой. Тогда удаление текущей строки не сдвигает индексы тех строк, которые ещё предстоит проверить:

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

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

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

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

КонецЕсли;

КонецЦикла;

Альтернатива с циклом Пока — счётчик увеличивается только если строка не была удалена:

Индекс = 0;

Пока Индекс < ТаблицаТоваров.Количество() Цикл

Если ТаблицаТоваров[Индекс].Количество = 0 Тогда

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

Иначе

Индекс = Индекс + 1;

КонецЕсли;

КонецЦикла;

Порядок действий при удалении строк
  1. 1
    Определите условие отбора строк для удаления
  2. 2
    Организуйте обход таблицы в обратном порядке по индексу или через цикл «Пока»
  3. 3
    Внутри цикла проверяйте условие и вызывайте Таблица.Удалить(Индекс)
  4. 4
    После цикла проверьте Таблица.Количество() — оно должно уменьшиться на число удалённых строк

Поиск строк без полного перебора: Найти, НайтиСтроки, ВыбратьСтроки

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

Метод Найти(Значение, Колонка) возвращает первую строку с указанным значением в заданной колонке или Неопределено, если совпадений нет:

СтрокаПоиска = ТаблицаТоваров.Найти(ИскомыйТовар, "Номенклатура");

Если СтрокаПоиска <> Неопределено Тогда

СтрокаПоиска.Найдено = Истина;

КонецЕсли;

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

Отбор = Новый Структура("Склад, Отгружен", СкладОсновной, Ложь);

СтрокиКОтгрузке = ТаблицаТоваров.НайтиСтроки(Отбор);

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

СтрокаОтгрузки.Отгружен = Истина;

КонецЦикла;

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

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

Для Каждого

Чтение и изменение всех строк по очереди. Не подходит для удаления строк внутри цикла.

Для по индексу

Нужен номер строки, доступ к соседним строкам или удаление при обратном обходе. Код длиннее и легче ошибиться с границами.

Найти / НайтиСтроки

Поиск строк по точному совпадению значений. Не работает с условиями «больше/меньше» и частичным совпадением.

Запрос к таблице

Сложные условия, группировки, соединения. Требует помещения таблицы во временную таблицу — оправдано только на больших объёмах.

Обход дерева и сгруппированной таблицы

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

Процедура ОбойтиДерево(СтрокиДерева)

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

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

ОбойтиДерево(СтрокаДерева.Строки);

КонецЦикла;

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

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

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

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

Итоги, сортировка и работа с колонками внутри цикла

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

ИтогОтгружено = 0;

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

Если СтрокаТовара.Отгружен Тогда

ИтогОтгружено = ИтогОтгружено + СтрокаТовара.Сумма;

КонецЕсли;

КонецЦикла;

Если порядок обхода важен — например, строки должны обрабатываться по дате или по убыванию суммы — таблицу предварительно сортируют методом Сортировать("Дата Возр, Сумма Убыв"). Сортировка меняет физический порядок строк, поэтому выполнять её нужно до начала цикла, а не внутри него.

Обращение к колонке внутри цикла возможно и по имени в квадратных скобках: СтрокаТовара["Сумма"]. Это полезно, когда имя колонки хранится в переменной — например, при универсальной обработке произвольных таблиц:

ИмяКолонки = "Сумма";

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

СтрокаТовара[ИмяКолонки] = СтрокаТовара[ИмяКолонки] * Коэффициент;

КонецЦикла;

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

Производительность на больших таблицах

На таблицах в десятки и сотни тысяч строк разница между подходами становится заметной. Самый дорогой антипаттерн — вложенный перебор: для каждой строки первой таблицы полный обход второй. При 10 000 строк в каждой таблице это уже 100 миллионов итераций.

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

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

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

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

КонецЦикла;

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

Цена = СоответствиеЦен[СтрокаТовара.Номенклатура];

Если Цена <> Неопределено Тогда

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

КонецЕсли;

КонецЦикла;

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

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

Как выбрать способ перебора

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

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

После написания цикла полезно проверить его на контролируемом наборе данных: создайте тестовую таблицу из 3–5 строк с известными значениями и просчитайте ожидаемый результат вручную. Совпадение с результатом кода — признак корректной работы.

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

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

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

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

Совпадает с Таблица.Количество() до перебора (или с ожидаемым числом отобранных строк)

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

Счётчик меньше — часть строк пропущена, например из-за удаления внутри «Для Каждого»

Итог по числовой колонке после цикла
Норма

Совпадает с Таблица.Итог() или ручным расчётом на тестовых данных

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

Расхождение — проверяйте условия пропуска строк и типы колонок

Изменения в исходной таблице
Норма

Значения строк изменились после цикла

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

Значения прежние — вероятно, изменялись копии из ВыбратьСтроки или локальные переменные

Поведение на пустой таблице
Норма

Цикл не выполняется, код после него отрабатывает без ошибок

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

Исключение или деление на ноль при расчёте средних значений

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

Как узнать индекс текущей строки в цикле «Для Каждого»?

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

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

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

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

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

Как перебрать только уникальные значения колонки?

Используйте Таблица.Скопировать(, "ИмяКолонки") для получения таблицы из одной колонки, затем вызовите у копии Свернуть("ИмяКолонки") — останутся только уникальные значения. Другой вариант — собирать значения в соответствие: ключи соответствия всегда уникальны.

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

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

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

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

Итог

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

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