Обход колонок ТаблицыЗначений: имена, индексы и значения ячеек в коде 1С

· 9 мин

Содержание

Колонки таблицы значений в 1С 8 перебирают через коллекцию Колонки: Для Каждого Колонка Из ТЗ.Колонки Цикл. Внутри цикла у вас есть объект колонки: Колонка.Имя, Колонка.Заголовок, Колонка.ТипЗначения. Значение ячейки в строке берут так: Строка[Колонка.Имя] или Строка[Индекс].

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

Как устроены колонки ТаблицыЗначений

У объекта ТаблицаЗначений есть свойство Колонки. Это отдельная коллекция типа КоллекцияКолонокТаблицыЗначений, а каждый её элемент — КолонкаТаблицыЗначений.

Свойства колонки, которые пригодятся при переборе:

  • 🏷️ Имя — идентификатор, по которому обращаются к ячейке. Латиница и кириллица допустимы, пробелы — нет.
  • 📝 Заголовок — человекочитаемая подпись. Если не задан, при выводе используется имя.
  • 🔢 ТипЗначения — объект ОписаниеТипов. Пустое описание означает «любой тип».
  • 📏 Ширина — ширина в символах, нужна для вывода в табличный документ и форму.

Методы самой коллекции колонок:

МетодЧто делает
Количество()Число колонок
Получить(Индекс) или Колонки[Индекс]Колонка по индексу, отсчёт с нуля
Найти(Имя)Колонка по имени или Неопределено
Индекс(Колонка)Номер колонки в коллекции
Добавить, Вставить, Удалить, Сдвинуть, ОчиститьИзменение состава

Строки таблицы (СтрокаТаблицыЗначений) поддерживают три способа доступа к ячейке: через точку (Строка.Сумма), по имени в квадратных скобках (Строка["Сумма"]) и по индексу (Строка[2]). Для динамического перебора подходят два последних.

Получаем список имён колонок

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

Функция ИменаКолонок(ТЗ) Экспорт

	Имена = Новый Массив;
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		Имена.Добавить(Колонка.Имя);
	КонецЦикла;

	Возврат Имена;

КонецФункции

// Строка вида "Номенклатура,Количество,Сумма"
СписокИмен = СтрСоединить(ИменаКолонок(ТЗ), ",");

СтрСоединить появилась в платформе 8.3.6. На более старых релизах склейку делают в том же цикле с разделителем.

Проверить, есть ли колонка с нужным именем, проще через Найти, а не перебором:

Если ТЗ.Колонки.Найти("Сумма") = Неопределено Тогда
	ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
КонецЕсли;

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

Цикл по индексу колонки

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

ЧислоКолонок = ТЗ.Колонки.Количество();

Для Индекс = 0 По ЧислоКолонок - 1 Цикл
	Колонка = ТЗ.Колонки[Индекс];
	Сообщить(Формат(Индекс, "ЧГ=0") + ": " + Колонка.Имя);
КонецЦикла;

Три детали, на которых ошибаются:

  1. Индексы начинаются с нуля. Верхняя граница — Количество() - 1, иначе последняя итерация упадёт.
  2. Количество колонок лучше сохранить в переменную до цикла. Вызов в заголовке цикла вычисляется один раз, но так код читается однозначнее, особенно если внутри цикла меняется состав колонок.
  3. Номер колонки при склейке со строкой оборачивайте в Формат(Индекс, "ЧГ=0"). Для чисел меньше 1000 разницы не видно, но на больших значениях без формата появится разделитель групп.

Удаление колонок в цикле

Если колонки нужно удалять по условию, идите с конца коллекции. При прямом обходе после удаления индексы сдвигаются, и одна колонка пропускается.

ЧислоКолонок = ТЗ.Колонки.Количество();

Для Сч = 1 По ЧислоКолонок Цикл
	Колонка = ТЗ.Колонки[ЧислоКолонок - Сч];
	Если СтрНачинаетсяС(Колонка.Имя, "Служебная") Тогда
		ТЗ.Колонки.Удалить(Колонка);
	КонецЕсли;
КонецЦикла;

Счётчик идёт от 1 до исходного числа колонок, а индекс считается от сохранённой границы. Так удаление не влияет на то, какие колонки уже проверены.

Перебор через «Для Каждого … Из Колонки»

Для большинства задач этого достаточно. Код короче, нет риска выйти за границу диапазона.

Для Каждого Колонка Из ТЗ.Колонки Цикл
	ТипыКолонки = Колонка.ТипЗначения.Типы();
	Сообщить(Колонка.Имя + " (" + Колонка.Заголовок + "), типов: "
		+ Формат(ТипыКолонки.Количество(), "ЧН=0; ЧГ=0"));
КонецЦикла;

Индекс текущей колонки внутри такого цикла при необходимости получают методом ТЗ.Колонки.Индекс(Колонка).

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

Если таблица — реквизит формы

На форме таблица значений хранится как ДанныеФормыКоллекция, и у неё нет свойства Колонки. На клиенте тип ТаблицаЗначений вообще недоступен. Рабочий приём — перейти на сервер и преобразовать реквизит:

&НаСервере
Функция ИменаКолонокТаблицыФормы()

	ТЗ = РеквизитФормыВЗначение("ТаблицаТоваров");
	Имена = Новый Массив;
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		Имена.Добавить(Колонка.Имя);
	КонецЦикла;
	Возврат Имена;

КонецФункции

Массив строк возвращается на клиент без проблем. Если вы меняли данные таблицы на сервере, верните их в форму через ЗначениеВРеквизитФормы(ТЗ, "ТаблицаТоваров").

Чтение и запись ячеек, когда имена колонок заранее неизвестны

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

Функция ТаблицаВТекст(ТЗ) Экспорт

	Имена = Новый Массив;
	Заголовки = Новый Массив;
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		Имена.Добавить(Колонка.Имя);
		Заголовки.Добавить(?(ПустаяСтрока(Колонка.Заголовок), Колонка.Имя, Колонка.Заголовок));
	КонецЦикла;

	Строки = Новый Массив;
	Строки.Добавить(СтрСоединить(Заголовки, ";"));

	Для Каждого СтрокаТЗ Из ТЗ Цикл
		Ячейки = Новый Массив;
		Для Каждого Имя Из Имена Цикл
			Ячейки.Добавить(Строка(СтрокаТЗ[Имя]));
		КонецЦикла;
		Строки.Добавить(СтрСоединить(Ячейки, ";"));
	КонецЦикла;

	Возврат СтрСоединить(Строки, Символы.ПС);

КонецФункции

Имена собраны один раз до обхода строк. При 10 колонках и 50 000 строках это экономит полмиллиона обращений к коллекции колонок.

Запись работает так же: СтрокаТЗ[Имя] = Значение. Есть и методы строки Получить(Индекс) и Установить(Индекс, Значение) — удобны, когда вы идёте по номерам.

Копирование совпадающих колонок

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

НоваяСтрока = ТЗПриемник.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);

Третьим параметром можно передать список имён через запятую, четвёртым — исключаемые колонки.

Строка таблицы в структуру

Для передачи строки между процедурами или на клиент её часто превращают в Структуру:

Функция СтрокаВСтруктуру(СтрокаТЗ) Экспорт

	Результат = Новый Структура;
	Для Каждого Колонка Из СтрокаТЗ.Владелец().Колонки Цикл
		Результат.Вставить(Колонка.Имя, СтрокаТЗ[Колонка.Имя]);
	КонецЦикла;
	Возврат Результат;

КонецФункции

Метод Владелец() возвращает таблицу, к которой принадлежит строка. Так функции не нужен второй параметр.

Типы значений в колонках и их приведение

Колонка, созданная без описания типов, принимает что угодно. Колонки из результата запроса получают составное описание типов, куда часто входит Null. Перед арифметикой или сравнением это стоит учитывать.

Проверить тип колонки и тип конкретного значения:

Для Каждого Колонка Из ТЗ.Колонки Цикл
	Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
		Итог = 0;
		Для Каждого СтрокаТЗ Из ТЗ Цикл
			Значение = СтрокаТЗ[Колонка.Имя];
			Если ТипЗнч(Значение) = Тип("Число") Тогда
				Итог = Итог + Значение;
			КонецЕсли;
		КонецЦикла;
		Сообщить(Колонка.Имя + ": " + Формат(Итог, "ЧДЦ=2"));
	КонецЕсли;
КонецЦикла;

Проверка ТипЗнч внутри цикла здесь не лишняя: колонка может допускать и число, и строку, и Null.

Чтобы привести значение к типу колонки, используйте метод описания типов:

СтрокаТЗ[Имя] = ТЗ.Колонки[Имя].ТипЗначения.ПривестиЗначение(Значение);

ПривестиЗначение вернёт значение по умолчанию для типа (0, пустую строку, пустую дату), если исходное к нему не приводится. Учитывайте это при загрузке данных из файла: строка, которую платформа не смогла распознать как число, молча превратится в 0. Надёжнее сначала явно преобразовать текст в число (с заменой запятой на точку, если нужно), а уже потом записывать в ячейку.

СитуацияЧто делать
Колонка из запроса, в данных встречается NullВ тексте запроса обернуть поле в ЕСТЬNULL(Поле, 0)
Нужна жёсткая типизация колонкиЗадать Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)) при добавлении
Вывод в текст ссылок и датСтрока(Значение) для ссылок, Формат(Дата, "ДФ=dd.MM.yyyy") для дат
Значение пустое, но не НеопределеноПроверять через ЗначениеЗаполнено()

Какой способ быстрее

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

Способ доступа к ячейкеСкоростьКогда применять
Строка.Сумма (имя в коде)Самый быстрыйСостав колонок известен заранее
Строка[Индекс]БыстрыйПеребор по номерам, две таблицы одинаковой структуры
Строка[Имя] из переменнойЧуть медленнее: поиск колонки по имениДинамический перебор, универсальные функции
ТЗ.Колонки.Найти(Имя) на каждую ячейкуМедленныйНе применять внутри цикла по строкам

Порядок цифр на вашей базе проверьте сами — замер занимает пару минут:

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
// ... проверяемый цикл ...
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Длительность, мс: " + Формат(Длительность, "ЧН=0; ЧГ=0"));

Что реально ускоряет обработку:

  • ⚡ собрать имена или индексы колонок в массив один раз, до цикла по строкам;
  • ⚡ не вызывать Найти и Количество() во внутреннем цикле;
  • ⚡ если нужна одна колонка целиком — брать ТЗ.ВыгрузитьКолонку("Сумма"), а не обходить строки;
  • ⚡ для итогов по числовой колонке — ТЗ.Итог("Сумма") вместо ручного суммирования;
  • ⚡ лишние колонки отбросить через ТЗ.Скопировать(, "Номенклатура,Сумма") до тяжёлой обработки.

Для отладочного просмотра в Конфигураторе перебор вообще не нужен: в режиме отладки табличные значения раскрываются в окне просмотра значения выражения.

Типичные ошибки при переборе колонок

Диагностика: выберите симптом

Где пригодится обход колонок: три готовых сценария

Сравнение двух строк. Нужно найти, какие поля изменились между версиями записи:

Функция ИзмененныеКолонки(СтрокаА, СтрокаБ) Экспорт

	Изменения = Новый Массив;
	Для Каждого Колонка Из СтрокаА.Владелец().Колонки Цикл
		Если СтрокаА[Колонка.Имя] <> СтрокаБ[Колонка.Имя] Тогда
			Изменения.Добавить(Колонка.Имя);
		КонецЕсли;
	КонецЦикла;
	Возврат Изменения;

КонецФункции

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

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

ЧислоКолонок = ТЗ.Колонки.Количество();

Для Сч = 1 По ЧислоКолонок Цикл
	Колонка = ТЗ.Колонки[ЧислоКолонок - Сч];
	ЕстьДанные = Ложь;
	Для Каждого СтрокаТЗ Из ТЗ Цикл
		Если ЗначениеЗаполнено(СтрокаТЗ[Колонка.Имя]) Тогда
			ЕстьДанные = Истина;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	Если Не ЕстьДанные Тогда
		ТЗ.Колонки.Удалить(Колонка);
	КонецЕсли;
КонецЦикла;

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

Результат = Запрос.Выполнить();
Для Каждого Колонка Из Результат.Колонки Цикл
	Сообщить(Колонка.Имя);
КонецЦикла;
Можно ли получить колонку по заголовку, а не по имени?
Встроенного метода нет. Переберите Колонки и сравните Колонка.Заголовок с искомой строкой; при частых обращениях соберите соответствие «заголовок → имя» один раз.
Как переименовать колонку при переборе?
Присвойте новое значение свойству Колонка.Имя. Новое имя не должно совпадать с уже существующим, иначе платформа выдаст ошибку.
Сохраняется ли порядок колонок?
Да. Колонки идут в порядке добавления, Вставить ставит колонку на нужную позицию, Сдвинуть перемещает её. Обход всегда идёт в текущем порядке.
Чем перебор колонок отличается от перебора строк?
Строки перебирают по самой таблице (Для Каждого СтрокаТЗ Из ТЗ), колонки — по свойству ТЗ.Колонки. Для полного обхода ячеек нужны оба цикла, вложенные друг в друга.
Как перебрать колонки дерева значений?
Так же: у ДеревоЗначений есть свойство Колонки с теми же методами, а к ячейке строки дерева обращаются по имени в квадратных скобках.