Как перебрать все колонки ТаблицыЗначений и не сломать данные
Содержание
Колонки таблицы значений в 1С перебирают через коллекцию Колонки: Для Каждого Колонка Из ТЗ.Колонки Цикл ... КонецЦикла. Значение ячейки в строке берут так: Строка[Колонка.Имя]. Этого хватает в девяти задачах из десяти — пока внутри цикла не начинают удалять колонки или не забывают, что у колонки может не быть типа.
Частое заблуждение — что у таблицы значений есть метаданные, как у справочника. Их нет: это объект в памяти, и всё, что о нём известно, лежит в самой коллекции колонок. Метаданные пригодятся, только если таблица выгружена из табличной части или регистра. Ниже — все методы обхода с рабочими примерами кода.
Таблица значений доступна на сервере и в толстом клиенте. В тонком клиенте и в веб-клиенте её нет, поэтому весь код ниже пишите в серверных процедурах (&НаСервере) или в общих модулях.
Простой перебор колонок через коллекцию Колонки
Коллекция ТЗ.Колонки — это объект КоллекцияКолонокТаблицыЗначений. Каждый её элемент — КолонкаТаблицыЗначений с четырьмя полезными свойствами.
| Свойство | Что хранит | Пример |
|---|---|---|
Имя | Идентификатор колонки, по нему обращаются к ячейке | Количество |
Заголовок | Текст для показа пользователю, может быть пустым | Кол-во, шт. |
ТипЗначения | Объект ОписаниеТипов | Число(15,3) |
Ширина | Ширина в символах при выводе | 10 |
Минимальный пример: вывести имена всех колонок и значения первой строки.
&НаСервере
Процедура ПоказатьКолонкиНаСервере()
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номенклатура = "Бумага А4";
НоваяСтрока.Количество = 5;
НоваяСтрока.Цена = 320;
Для Каждого Колонка Из ТЗ.Колонки Цикл
Сообщить(Колонка.Имя + " = " + Строка(ТЗ[0][Колонка.Имя]));
КонецЦикла;
КонецПроцедуры
Чтобы пройти всю таблицу, нужен вложенный цикл: внешний по строкам, внутренний по колонкам.
Для Каждого СтрокаТЗ Из ТЗ Цикл
Для Каждого Колонка Из ТЗ.Колонки Цикл
Значение = СтрокаТЗ[Колонка.Имя];
// обработка значения
КонецЦикла;
КонецЦикла;
Другие методы коллекции, которые нужны при обходе:
- 🔢
Количество()— число колонок; - 🔍
Найти("Имя")— колонка по имени илиНеопределено, если её нет; - 📍
Индекс(Колонка)— позиция колонки, отсчёт с нуля; - 📥
Получить(Индекс)— колонка по номеру.
Перебор колонок с проверкой типа значения
Свойство ТипЗначения возвращает ОписаниеТипов, а не один тип. Поэтому сравнивать его с Тип("Число") напрямую бессмысленно. Для проверки есть метод СодержитТип().
Для Каждого Колонка Из ТЗ.Колонки Цикл
Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
Сообщить("Числовая колонка: " + Колонка.Имя
+ ", итог: " + Формат(ТЗ.Итог(Колонка.Имя), "ЧДЦ=2"));
ИначеЕсли Колонка.ТипЗначения.СодержитТип(Тип("Дата")) Тогда
Сообщить("Колонка даты: " + Колонка.Имя);
КонецЕсли;
КонецЦикла;
Три ситуации, которые ломают такую проверку:
- Колонка добавлена без типа. Вызов
ТЗ.Колонки.Добавить("Сумма")создаёт колонку с пустым описанием типов. СписокКолонка.ТипЗначения.Типы()пуст,СодержитТип()вернётЛожьдля любого типа, а хранить в колонке можно что угодно. - Таблица выгружена из запроса. Колонки результата запроса часто содержат в описании ещё и тип
Null.СодержитТип(Тип("Число"))при этом вернётИстина, но в ячейке может оказатьсяNull, и арифметика упадёт. - Составной тип. Колонка может хранить и число, и строку. Тогда проверяйте тип конкретного значения:
ТипЗнч(СтрокаТЗ[Колонка.Имя]) = Тип("Число").
Для пустых колонок без типа надёжнее смотреть на само значение, а не на описание колонки:
Значение = СтрокаТЗ[Колонка.Имя];
Если ТипЗнч(Значение) = Тип("Число") Тогда
Сумма = Сумма + Значение;
КонецЕсли;
Имена и типы из метаданных: таблица из табличной части
У самой таблицы значений метаданных нет. Но если вы получили её методом Выгрузить() из табличной части документа, колонки совпадают с реквизитами этой табличной части. Плюс есть служебная колонка НомерСтроки.
Из метаданных удобно брать синоним реквизита — понятное название для заголовка отчёта или файла:
&НаСервере
Функция ЗаголовкиКолонок(ТЗ, МетаТабЧасть)
Заголовки = Новый Соответствие;
Для Каждого Колонка Из ТЗ.Колонки Цикл
МетаРеквизит = МетаТабЧасть.Реквизиты.Найти(Колонка.Имя);
Если МетаРеквизит <> Неопределено Тогда
Заголовки.Вставить(Колонка.Имя, МетаРеквизит.Синоним);
ИначеЕсли ЗначениеЗаполнено(Колонка.Заголовок) Тогда
Заголовки.Вставить(Колонка.Имя, Колонка.Заголовок);
Иначе
Заголовки.Вставить(Колонка.Имя, Колонка.Имя);
КонецЕсли;
КонецЦикла;
Возврат Заголовки;
КонецФункции
Вызов для табличной части документа, где Документ — ссылка или объект:
МетаТЧ = Документ.Метаданные().ТабличныеЧасти.Товары;
ТЗ = Документ.Товары.Выгрузить();
Заголовки = ЗаголовкиКолонок(ТЗ, МетаТЧ);
Если НомерСтроки в выгрузке не нужен, сразу удалите его: ТЗ.Колонки.Удалить("НомерСтроки"). Иначе он попадёт во все экспорты и проверки.
Тот же приём работает с регистрами: у метаданных регистра есть коллекции Измерения, Ресурсы и Реквизиты. Ищите имя колонки по очереди в каждой из них.
Цикл по индексам колонок вместо Для Каждого
Строка таблицы значений понимает обращение не только по имени, но и по индексу колонки: СтрокаТЗ[0] — значение первой колонки. Когда колонок много и нужна позиция, цикл по счётчику удобнее.
ЧислоКолонок = ТЗ.Колонки.Количество();
Для Каждого СтрокаТЗ Из ТЗ Цикл
Для Инд = 0 По ЧислоКолонок - 1 Цикл
Значение = СтрокаТЗ[Инд];
// обработка значения
КонецЦикла;
КонецЦикла;
Что здесь сделано ради скорости:
- ⚡ количество колонок вычислено один раз до циклов, а не в каждой итерации;
- ⚡ нет поиска колонки по имени на каждой ячейке;
- ⚡ индексы совпадают с номерами колонок в табличном документе (со сдвигом на единицу), это удобно при выводе.
Ещё быстрее — не перебирать ячейки вообще, если задача решается целой колонкой:
| Задача | Вместо цикла по ячейкам | Что возвращает |
|---|---|---|
| Сумма по колонке | ТЗ.Итог("Сумма") | Число |
| Все значения колонки | ТЗ.ВыгрузитьКолонку("Номенклатура") | Массив |
| Записать массив в колонку | ТЗ.ЗагрузитьКолонку(Массив, "Цена") | — |
| Заполнить колонку одним значением | ТЗ.ЗаполнитьЗначения(0, "Скидка") | — |
| Группировка с суммами | ТЗ.Свернуть("Номенклатура", "Количество, Сумма") | — |
| Копия только нужных колонок | ТЗ.Скопировать(, "Номенклатура, Цена") | ТаблицаЗначений |
Колонки, которые добавляются в коде на лету
Типичный пример — колонки по месяцам или по складам: заранее неизвестно, сколько их будет. Имена собирают из префикса и номера.
Для НомерМесяца = 1 По 12 Цикл
ИмяКолонки = "Месяц" + Формат(НомерМесяца, "ЧЦ=2; ЧВН=; ЧГ=0");
Если ТЗ.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
ТЗ.Колонки.Добавить(ИмяКолонки, Новый ОписаниеТипов("Число"),
Формат(Дата(2000, НомерМесяца, 1), "ДФ=ММММ"));
КонецЕсли;
КонецЦикла;
Получатся колонки Месяц01 … Месяц12 с заголовками-названиями месяцев. Год в Дата(2000, ...) не важен — он нужен только для форматирования.
Правила для имён таких колонок:
- ✅ начинаются с буквы или подчёркивания;
- ✅ без пробелов, точек, дефисов;
- ✅ числа в имени через
Формат(..., "ЧГ=0"), иначе для 1000 и больше получится неразрывный пробел-разделитель и ошибка имени; - ✅ перед добавлением — проверка
Найти(), чтобы не получить ошибку дубля.
Новые колонки появляются во всех уже существующих строках со значением по умолчанию для своего типа. У колонки без типа это Неопределено.
Обход только «динамической» части удобно делать по префиксу имени:
Для Каждого Колонка Из ТЗ.Колонки Цикл
Если СтрНачинаетсяС(Колонка.Имя, "Месяц") Тогда
Сообщить(Колонка.Заголовок + ": " + Формат(ТЗ.Итог(Колонка.Имя), "ЧГ=0"));
КонецЕсли;
КонецЦикла;
Удаление колонок при обходе
Удалять колонки внутри Для Каждого по той же коллекции нельзя: перебор собьётся и часть колонок будет пропущена. Используйте обратный цикл:
Всего = ТЗ.Колонки.Количество();
Для Сч = 1 По Всего Цикл
Колонка = ТЗ.Колонки[Всего - Сч];
Если ТЗ.ВыгрузитьКолонку(Колонка).Найти(Неопределено) <> Неопределено Тогда
ТЗ.Колонки.Удалить(Колонка);
КонецЕсли;
КонецЦикла;
Пример удаляет колонки, где хотя бы в одной ячейке Неопределено. Условие замените на своё. Граница Всего вычислена до цикла, индекс идёт с конца, поэтому удаление не сдвигает ещё не проверенные колонки.
ОписаниеТипов в деле: приведение и разбор составных типов
Описание типов умеет не только отвечать на вопрос «содержит ли». Три метода, которые пригодятся при универсальной обработке колонок:
- 📋
Типы()— массив всех типов колонки. Пустой массив — колонка без типа; - 🔄
ПривестиЗначение(Значение)— приводит значение к типу колонки. Если значение не подходит, вернётся значение по умолчанию: для числа — 0, для строки — пустая строка; - 📏
КвалификаторыЧисла,КвалификаторыСтроки,КвалификаторыДаты— разрядность, длина, состав даты.
Пример: вывести полное описание каждой колонки, включая составные типы.
Для Каждого Колонка Из ТЗ.Колонки Цикл
ИменаТипов = Новый Массив;
Для Каждого ТекТип Из Колонка.ТипЗначения.Типы() Цикл
ИменаТипов.Добавить(Строка(ТекТип));
КонецЦикла;
Если ИменаТипов.Количество() = 0 Тогда
ИменаТипов.Добавить("<любой тип>");
КонецЕсли;
Сообщить(Колонка.Имя + ": " + СтрСоединить(ИменаТипов, ", "));
КонецЦикла;
Приведение значений перед записью в таблицу защищает от «грязных» данных, например при загрузке из файла:
Для Каждого Колонка Из ТЗ.Колонки Цикл
СтрокаТЗ[Колонка.Имя] = Колонка.ТипЗначения.ПривестиЗначение(СтрокаТЗ[Колонка.Имя]);
КонецЦикла;
У колонки без типа ПривестиЗначение() ничего не исправит. Задавайте тип колонкам, которые заполняются из внешних файлов, иначе ошибка всплывёт позже — при расчёте или записи в базу.
Готовые задачи: выгрузка в Excel и CSV, проверка заполнения
Таблица значений в табличный документ и Excel
Первая строка — заголовки, дальше — данные. Ячейки адресуются по имени области вида R1C1.
&НаСервере
Функция ТЗВТабличныйДокумент(ТЗ)
ТабДок = Новый ТабличныйДокумент;
НомКол = 0;
Для Каждого Колонка Из ТЗ.Колонки Цикл
НомКол = НомКол + 1;
Ячейка = ТабДок.Область("R1C" + Формат(НомКол, "ЧГ=0"));
Ячейка.Текст = ?(ЗначениеЗаполнено(Колонка.Заголовок), Колонка.Заголовок, Колонка.Имя);
КонецЦикла;
НомСтр = 1;
Для Каждого СтрокаТЗ Из ТЗ Цикл
НомСтр = НомСтр + 1;
НомКол = 0;
Для Каждого Колонка Из ТЗ.Колонки Цикл
НомКол = НомКол + 1;
Ячейка = ТабДок.Область("R" + Формат(НомСтр, "ЧГ=0") + "C" + Формат(НомКол, "ЧГ=0"));
Ячейка.Текст = Строка(СтрокаТЗ[Колонка.Имя]);
КонецЦикла;
КонецЦикла;
Возврат ТабДок;
КонецФункции
Сохранить результат в файл Excel: ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX). Код выполняется на сервере, поэтому и файл ляжет на сервер. Пользователю его передают через временное хранилище и получение файла на клиенте.
Выгрузка в CSV
CSV удобен, когда файл забирает другая программа. Значения строки собираются в массив и склеиваются разделителем.
Имена = Новый Массив;
Для Каждого Колонка Из ТЗ.Колонки Цикл
Имена.Добавить(Колонка.Имя);
КонецЦикла;
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку(СтрСоединить(Имена, ";"));
Для Каждого СтрокаТЗ Из ТЗ Цикл
Значения = Новый Массив;
Для Каждого Колонка Из ТЗ.Колонки Цикл
Значения.Добавить(СтрЗаменить(Строка(СтрокаТЗ[Колонка.Имя]), ";", ","));
КонецЦикла;
Запись.ЗаписатьСтроку(СтрСоединить(Значения, ";"));
КонецЦикла;
Запись.Закрыть();
Замена ; на , внутри значений нужна, чтобы точка с запятой в названии товара не разорвала строку файла на лишние поля.
Проверка, что все обязательные колонки заполнены
Обязательные = СтрРазделить("Номенклатура,Количество,Цена", ",");
Для Каждого СтрокаТЗ Из ТЗ Цикл
Для Каждого ИмяКолонки Из Обязательные Цикл
Если ТЗ.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
ВызватьИсключение "В таблице нет колонки " + ИмяКолонки;
КонецЕсли;
Если Не ЗначениеЗаполнено(СтрокаТЗ[ИмяКолонки]) Тогда
Сообщить(СтрШаблон("Строка %1: не заполнено поле %2",
Формат(ТЗ.Индекс(СтрокаТЗ) + 1, "ЧГ=0"), ИмяКолонки));
КонецЕсли;
КонецЦикла;
КонецЦикла;
ЗначениеЗаполнено() считает незаполненными ноль, пустую строку, пустую дату, пустую ссылку и Неопределено. Если ноль в колонке допустим, проверяйте его отдельно через ТипЗнч.
Ошибки при обходе колонок и как их найти
ТЗ.Колонки.Найти(Имя) перед обращением.&НаСервере.Null или строка. Проверьте тип значения через ТипЗнч или приведите его через ПривестиЗначение().Для Каждого по той же коллекции. Перейдите на обратный цикл по индексу.Формат(Число, "ЧГ=0").Отдельно про форму. Реквизит формы с типом ТаблицаЗначений на клиенте — это ДанныеФормыКоллекция, у неё нет коллекции Колонки. Чтобы перебрать колонки, на сервере преобразуйте реквизит: ТЗ = РеквизитФормыВЗначение("ТаблицаТоваров"), обработайте, затем верните ЗначениеВРеквизитФормы(ТЗ, "ТаблицаТоваров").
Частые вопросы
Как узнать, есть ли в таблице значений колонка с нужным именем?
ТЗ.Колонки.Найти("Имя") вернёт колонку или Неопределено. Сравните результат с Неопределено до обращения к ячейке.Как переименовать колонку при обходе?
Имя: Колонка.Имя = "НовоеИмя". Данные в строках сохранятся, меняется только идентификатор.Как поменять порядок колонок?
ТЗ.Колонки.Сдвинуть(Колонка, Смещение): положительное смещение двигает колонку вправо, отрицательное — влево.Можно ли получить значения строки сразу все, без цикла по колонкам?
ЗаполнитьЗначенияСвойств(Структура, СтрокаТЗ), но ключи структуры нужно создать заранее — тем же циклом по колонкам.Чем обход колонок таблицы значений отличается от обхода дерева значений?
Колонки. Разница в строках: у дерева они вложенные, и для полного обхода нужна рекурсия по Строки каждой строки.