Обход строк и колонок ТаблицыЗначений во встроенном языке 1С без лишних тормозов
Содержание
Перебрать таблицу значений в 1С проще всего циклом Для Каждого Строка Из Таблица Цикл … КонецЦикла: на каждой итерации переменная Строка получает очередную строку, а к полям обращаются через точку — Строка.Сумма. Если нужен номер строки или строки придётся удалять, берите цикл по индексу Для Индекс = 0 По Таблица.Количество() - 1.
Проблемы начинаются не с синтаксиса, а с объёма. Таблица на 50 000 строк и вложенный цикл поиска внутри — и обработка, которая на тестовой базе отрабатывала за секунду, на рабочей висит десять минут. Ниже — как обходить ТаблицуЗначений правильно, где перебор вообще не нужен и какие ошибки чаще всего ломают код.
Как устроена ТаблицаЗначений и где она доступна
ТаблицаЗначений — универсальная коллекция встроенного языка 1С:Предприятия 8.3. Её задача — держать в памяти набор однотипных записей: результат запроса, данные для печати, промежуточные расчёты.
Объект состоит из трёх частей:
- 📋 Колонки — коллекция
Таблица.Колонки. У каждой колонки есть имя, заголовок и тип значения (ОписаниеТипов). - 📄 Строки — элементы типа
СтрокаТаблицыЗначений. Сама таблица и есть коллекция строк, отдельного свойства «Строки» у неё нет. - ⚡ Индексы — коллекция
Таблица.Индексы. Ускоряют поиск по выбранным колонкам.
Создание таблицы вручную:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
Таблица.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
НоваяСтрока = Таблица.Добавить();
НоваяСтрока.Количество = 3;
НоваяСтрока.Сумма = 1500;
Чаще таблица приходит готовой: Запрос.Выполнить().Выгрузить() или ТабличнаяЧасть.Выгрузить() у документа.
ТаблицаЗначений работает на сервере и в толстом клиенте. В управляемой форме на тонком клиенте её нет: реквизит формы типа «ТаблицаЗначений» на клиенте виден как ДанныеФормыКоллекция. Чтобы получить настоящую таблицу, вызовите на сервере РеквизитФормыВЗначение("ИмяРеквизита"), а вернуть изменения — через ЗначениеВРеквизитФормы.
Отсюда практическое правило: тяжёлый перебор делайте в серверной процедуре с директивой &НаСервере или в общем модуле, а на клиент передавайте только итог.
Перебор строк таблицы значений циклом Для Каждого
Самый короткий и читаемый вариант. Подходит для 90% задач, где строки только читаются или меняются их поля.
ИтогоСумма = 0;
Для Каждого Строка Из Таблица Цикл
Если Строка.Количество = 0 Тогда
Продолжить;
КонецЕсли;
Строка.Сумма = Строка.Количество * Строка.Цена;
ИтогоСумма = ИтогоСумма + Строка.Сумма;
КонецЦикла;
Что здесь важно:
- ✏️
Строка— ссылка на строку таблицы, а не копия. ПрисваиваниеСтрока.Сумма = …меняет саму таблицу. - ⏭️
Продолжитьпропускает текущую итерацию,Прерватьвыходит из цикла целиком. - 🔢 Номер текущей строки при необходимости даёт
Таблица.Индекс(Строка), но звать его на каждой итерации расточительно: если номер нужен постоянно, переходите на цикл по индексу.
Для простой суммы по колонке цикл не нужен: Таблица.Итог("Сумма") вернёт сумму числовой колонки одной строкой кода.
Главное ограничение: в цикле Для Каждого нельзя менять состав коллекции. Удаление строк внутри Для Каждого приводит к пропуску соседних строк — после удаления следующая строка сдвигается на место текущей, и цикл её перешагивает. Ошибки при этом нет, и именно поэтому баг долго живёт в коде.
Цикл по индексу: Для … По … и обратный обход
Строки таблицы нумеруются с нуля. Последняя строка имеет индекс Таблица.Количество() - 1. К строке обращаются через квадратные скобки или метод Получить():
Для Индекс = 0 По Таблица.Количество() - 1 Цикл
Строка = Таблица[Индекс]; // то же, что Таблица.Получить(Индекс)
Сообщить(СтрШаблон("%1: %2", Индекс + 1, Строка.Номенклатура));
КонецЦикла;
Для пустой таблицы граница получится -1, и цикл просто не выполнится ни разу — дополнительная проверка не нужна.
Обход с шагом и сравнение соседних строк
У цикла Для … По шаг всегда равен единице. Если нужна каждая вторая или каждая десятая строка, используйте Пока со своим счётчиком:
Шаг = 10;
Индекс = 0;
Пока Индекс < Таблица.Количество() Цикл
Строка = Таблица[Индекс];
// обработка каждой десятой строки
Индекс = Индекс + Шаг;
КонецЦикла;
Сравнение с предыдущей строкой — типичная задача после сортировки, например, чтобы найти смену контрагента и вставить промежуточный итог. Начинайте счётчик с единицы, чтобы Таблица[Индекс - 1] не вышел за границу:
Таблица.Сортировать("Контрагент");
Для Индекс = 1 По Таблица.Количество() - 1 Цикл
Если Таблица[Индекс].Контрагент <> Таблица[Индекс - 1].Контрагент Тогда
// здесь начинается группа следующего контрагента
КонецЕсли;
КонецЦикла;
Удаление строк при переборе
Удалять безопасно только с конца. Тогда удаление строки не сдвигает те, что ещё не проверены:
ВсегоСтрок = Таблица.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
Строка = Таблица[ВсегоСтрок - Сч];
Если Строка.Количество = 0 Тогда
Таблица.Удалить(Строка);
КонецЕсли;
КонецЦикла;
Индекс считается от зафиксированного до цикла ВсегоСтрок. При первом проходе берётся строка ВсегоСтрок - 1, при последнем — строка с индексом 0. Удаление уже пройденных хвостовых строк на оставшиеся индексы не влияет.
Альтернатива без обратного цикла — сначала собрать строки в массив, потом удалить:
КУдалению = Таблица.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого Строка Из КУдалению Цикл
Таблица.Удалить(Строка);
КонецЦикла;
Здесь цикл идёт по массиву, а не по таблице, поэтому пропусков нет.
Когда какой цикл брать
| Задача | Цикл | Почему |
|---|---|---|
| Прочитать или изменить поля | Для Каждого | Короче, нет риска ошибиться с границей |
| Нужен номер строки | Для … По | Индекс уже есть в счётчике |
| Сравнить строку с соседней | Для … По | Доступ к Таблица[Индекс - 1] |
| Удалить часть строк | Обратный Для … По или массив из НайтиСтроки | Нет пропусков |
| Обработать каждую N-ю строку | Пока с ручным счётчиком | У Для … По шаг всегда равен 1 |
Обход колонок и доступ к полям строки
Иногда имена колонок заранее неизвестны — например, таблица пришла из универсальной выгрузки. Тогда перебирают коллекцию Колонки:
Для Каждого Строка Из Таблица Цикл
Для Каждого Колонка Из Таблица.Колонки Цикл
Значение = Строка[Колонка.Имя];
// обработка значения
КонецЦикла;
КонецЦикла;
К полю строки можно обратиться тремя способами:
Строка.Сумма— через точку, имя известно при написании кода.Строка["Сумма"]— по имени в строке, когда имя колонки хранится в переменной.Строка[2]— по индексу колонки, если порядок колонок фиксирован.
Третий способ хрупкий: стоит добавить колонку в начало, и код молча начнёт читать чужое поле. Используйте его только в тех местах, где структура таблицы создаётся тут же, в этой процедуре.
Как не делать лишнюю работу на каждой строке
- 🧮 Выносите из цикла всё, что не зависит от строки. Получение константы, ставки НДС, даты, реквизита организации — до цикла, в переменную.
- 🚫 Не обращайтесь к реквизитам через точку от ссылки.
Строка.Номенклатура.ЕдиницаИзмерениявнутри цикла — это неявное чтение объекта из базы на каждой строке. Нужные реквизиты заберите в исходный запрос отдельными колонками. - 📦 Выгружайте колонку целиком, если нужен только список значений.
Таблица.ВыгрузитьКолонку("Номенклатура")вернёт массив без ручного цикла. Обратная операция —ЗагрузитьКолонку(Массив, "Колонка"). - 🧹 Заполняйте колонку одним вызовом.
Таблица.ЗаполнитьЗначения(0, "Сумма")обнулит колонку быстрее и понятнее, чем цикл.
Поиск и отбор строк таблицы значений без полного перебора
Главная причина медленных обработок — вложенные циклы. Для каждой строки первой таблицы перебирается вся вторая: 10 000 × 10 000 — уже сто миллионов сравнений. Встроенные методы и индексы снимают эту проблему.
НайтиСтроки и Найти
НайтиСтроки(Структура) возвращает массив строк, у которых значения колонок совпадают с отбором:
Отбор = Новый Структура("Номенклатура, Склад", ТоварСсылка, СкладСсылка);
Найденные = Таблица.НайтиСтроки(Отбор);
Если Найденные.Количество() > 0 Тогда
Найденные[0].Количество = Найденные[0].Количество + 1;
КонецЕсли;
Найденные строки — это строки самой таблицы, их изменение меняет исходник.
Найти(Значение, "Колонка1, Колонка2") ищет первую строку, где значение встречается в любой из перечисленных колонок, и возвращает строку либо Неопределено. Удобен для проверки «есть ли такой товар вообще».
Индексы
Если поиск по одним и тем же колонкам повторяется в цикле, добавьте индекс один раз перед циклом:
Таблица.Индексы.Добавить("Номенклатура, Склад");
После этого НайтиСтроки с отбором по этим колонкам перестаёт просматривать таблицу целиком. Индекс стоит памяти и пересчитывается при добавлении строк, поэтому ставьте его после заполнения таблицы и только под реальные отборы. На таблице в 30 строк индекс не нужен.
Соответствие вместо поиска
Когда ключ один и уникален, удобнее построить Соответствие одним проходом:
Цены = Новый Соответствие;
Для Каждого Строка Из ТаблицаЦен Цикл
Цены.Вставить(Строка.Номенклатура, Строка.Цена);
КонецЦикла;
Для Каждого Строка Из ТаблицаТоваров Цикл
Цена = Цены.Получить(Строка.Номенклатура);
Строка.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
Вместо вложенного цикла — два последовательных прохода.
Готовые операции над всей таблицей
| Нужно | Метод | Вместо |
|---|---|---|
| Свернуть дубли и просуммировать | Свернуть("Номенклатура", "Количество, Сумма") | Цикл с поиском и сложением |
| Отсортировать | Сортировать("Сумма Убыв, Номенклатура") | Ручная сортировка |
| Получить подмножество строк | Скопировать(Отбор, "Колонки") | Цикл с добавлением в новую таблицу |
| Сумма по колонке | Итог("Сумма") | Цикл-накопитель |
| Уникальные значения | Скопировать(, "Колонка") и Свернуть("Колонка") | Цикл с проверкой на дубль |
Скопировать принимает первым параметром массив строк или структуру отбора, вторым — список нужных колонок через запятую. Без параметров создаёт полную копию.
Запрос к таблице значений
Сложные соединения двух таблиц проще отдать запросу. ТаблицуЗначений передают параметром и сначала помещают во временную таблицу:
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| &ТаблицаТоваров КАК Т";
Запрос.УстановитьПараметр("ТаблицаТоваров", Таблица);
Запрос.Выполнить();
Дальше с ВТТовары можно соединять регистры и справочники, как с обычной таблицей.
Колонки, которые используются в запросе, должны иметь заданный тип. Если колонку создали через Колонки.Добавить("Имя") без ОписаниеТипов, запрос выдаст ошибку о недопустимом типе. Указывайте тип при создании колонки.
Ошибки, на которых спотыкаются при обходе ТаблицыЗначений
Ещё два антипаттерна, которые не дают ошибки, но съедают ресурсы:
- 🔁 Запрос внутри цикла по строкам. Тысяча строк — тысяча обращений к базе. Соберите все ключи массивом через
ВыгрузитьКолонкуи выполните один запрос с условиемВ (&Массив). - 🧱 Добавление строк по одной в таблицу, которую можно получить запросом. Если данные лежат в базе, сразу выгружайте результат запроса, а не заполняйте таблицу вручную из выборки.