Смена типа колонки в таблице значений: пересоздание и конвертация

· 10 мин

Содержание

У созданной колонки таблицы значений свойство Тип доступно только для чтения. Присвоить ему новое значение нельзя, поэтому «изменить тип» на деле означает завести колонку (или всю таблицу) с нужным типом, перенести туда данные и убрать старую колонку.

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

Почему тип колонки нельзя поменять на месте

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

Что полезно помнить про типизацию:

  • 🔹 Колонка без указания типа принимает значение любого типа. Её не нужно «менять» — достаточно записать в ячейку другое значение.
  • 🔹 Типизированная колонка при записи значения приводит его к своему типу. Строка, записанная в числовую колонку, превратится в 0, если не распознается как число.
  • 🔹 Тип задаёт ОписаниеТипов: набор типов плюс квалификаторы длины строки, разрядности числа, состава даты.

Если тип нужен только для красоты отображения в форме, часто проще оставить колонку без типа и привести значения при заполнении. Типизация нужна, когда вы хотите, чтобы платформа сама ограничивала ввод и выбирала подходящий элемент редактирования.

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

Это самый безопасный вариант для небольших таблиц. Исходная колонка остаётся до последнего шага, так что при ошибке данные не потеряны.

Порядок действий:

  1. Опишите нужный тип через ОписаниеТипов.
  2. Добавьте колонку с временным именем и этим описанием.
  3. Пройдите по строкам и запишите в новую колонку приведённое значение.
  4. Удалите старую колонку.
  5. Если нужно прежнее имя, соберите таблицу заново (см. следующий способ): имя уже созданной колонки лучше не менять.
Процедура ПоменятьТипКолонки(Таблица, ИмяКолонки, НовоеОписание)
	
	ВремИмя = ИмяКолонки + "_Нов";
	Таблица.Колонки.Добавить(ВремИмя, НовоеОписание);
	
	Для Каждого Строка Из Таблица Цикл
		Строка[ВремИмя] = НовоеОписание.ПривестиЗначение(Строка[ИмяКолонки]);
	КонецЦикла;
	
	Таблица.Колонки.Удалить(ИмяКолонки);
	
КонецПроцедуры

// Вызов: колонка «Сумма» была строковой, нужно число 15,2
Описание = Новый ОписаниеТипов("Число",
	Новый КвалификаторыЧисла(15, 2, ДопустимыйЗнак.Неотрицательный));
ПоменятьТипКолонки(ТаблицаДанных, "Сумма", Описание);

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

⚠️ Не удаляйте колонку, пока не убедились, что все значения перенеслись. Проверьте на реальных данных, а не на одной строке.

Способ 2: пересоздать таблицу с нужной структурой

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

Схема такая: новая таблица получает те же колонки, что и старая, кроме изменяемой. Для неё задаётся новое описание типов. Затем строки переносятся по одной.

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

Поскольку колонки новой таблицы типизированы, присваивание НоваяСтрока[Колонка.Имя] = ... само приведёт значение к новому типу там, где он изменился. Отдельно вызывать ПривестиЗначение не обязательно, но для тех случаев, где нужен контроль результата, он пригодится (см. раздел про ошибки).

Сравнение двух способов:

СпособКогда подходитРиски
Новая колонка + удаление старойМалые таблицы, имя колонки не критичноМеняется имя и положение колонки
Пересоздание таблицыНужны прежние имена, порядок, заголовкиДвойное потребление памяти на время работы

Как правильно собрать ОписаниеТипов

Половина проблем со сменой типа возникает из-за неточного описания. Платформа допускает несколько способов его задать.

Простые типы строкой:

Описание = Новый ОписаниеТипов("Строка");

Строка с ограничением длины:

Описание = Новый ОписаниеТипов("Строка",,
	Новый КвалификаторыСтроки(100, ДопустимаяДлина.Переменная));

Число с точностью, дата без времени:

ОписаниеЧисла = Новый ОписаниеТипов("Число",
	Новый КвалификаторыЧисла(12, 3));
ОписаниеДаты = Новый ОписаниеТипов("Дата",,,
	Новый КвалификаторыДаты(ЧастиДаты.Дата));

Ссылки и составные типы:

Описание = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
Составной = Новый ОписаниеТипов("СправочникСсылка.Контрагенты,СправочникСсылка.ФизическиеЛица");

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

Если нужно расширить уже существующий тип (скажем, добавить Неопределено или длину побольше), возьмите описание старой колонки как основу: у ОписаниеТипов есть конструктор, принимающий исходное описание, добавляемые и вычитаемые типы. Так вы не потеряете то, что уже разрешено.

Конвертация данных в цикле: как привести значения

Приведение выполняет метод ПривестиЗначение у ОписаниеТипов. Он возвращает значение нужного типа. Если исходное значение привести нельзя, вернётся значение типа по умолчанию: 0 для числа, пустая строка, пустая дата, пустая ссылка.

Это удобно, но опасно: потеря данных проходит молча. Строка «ООО Ромашка» в числовой колонке даст 0, и никакого сообщения вы не увидите.

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

Проблемные = Новый Массив;
Номер = 0;

Для Каждого Строка Из Таблица Цикл
	Номер = Номер + 1;
	Исходное = Строка[ИмяКолонки];
	Приведённое = НовоеОписание.ПривестиЗначение(Исходное);
	
	Если ТипЗнч(Исходное) <> ТипЗнч(Приведённое) Или Не ЗначениеЗаполнено(Приведённое)
		И ЗначениеЗаполнено(Исходное) Тогда
		Проблемные.Добавить(Номер);
		Продолжить;
	КонецЕсли;
	
	Строка[ВремИмя] = Приведённое;
КонецЦикла;

Номер строки считаем счётчиком, а не поиском по таблице: так цикл остаётся быстрым. Идея проверки проста: отдельно отмечаем строки, где заполненное значение превратилось в пустое или сменило тип не так, как задумано. Их разбирают вручную или правят правилом, а не оставляют как есть.

Если исходная колонка содержала пустые значения, они пустыми и останутся. Это нормально, и в список проблем такие строки попадать не должны: условие выше проверяет ЗначениеЗаполнено(Исходное).

Что стоит учесть при разных парах типов:

  • 🔸 Строка → число. Разделитель дробной части и пробелы в записи могут помешать распознаванию. Проверьте на своих данных и при необходимости предварительно подчистите строку функциями СтрЗаменить и СокрЛП.
  • 🔸 Строка → дата. Платформа распознаёт строки определённого вида (например, из цифр ГГГГММДД). Произвольный текст даст пустую дату. Для нестандартных форматов разбирайте строку самостоятельно и собирайте Дата(Год, Месяц, День).
  • 🔸 Число → строка. Приведение сработает всегда. Если важен формат без разделителей групп, используйте Формат(Число, "ЧГ=0").
  • 🔸 Строка → ссылка. Автоматически не получится: строку нужно искать в справочнике (НайтиПоНаименованию, НайтиПоКоду или запрос) и подставлять найденную ссылку.

Обработка ошибок при смене типа

Ошибки при смене типа бывают двух родов: исключения и тихая потеря значений.

Исключения. Они возникают, когда вы вызываете преобразования вроде Число(Строка) напрямую. Такой вызов падает на нечисловой строке. Оберните его в Попытка ... Исключение, а в ветке исключения запишите значение по умолчанию и пометьте строку:

Попытка
	Значение = Число(Строка(Исходное));
Исключение
	Значение = 0;
	Проблемные.Добавить(Номер);
КонецПопытки;

Тихая потеря. Её дают ПривестиЗначение и присваивание в типизированную колонку. Страхует сравнение до и после, как показано выше.

После прохода по таблице покажите пользователю или запишите в журнал список проблемных строк. Для этого достаточно номера строки и исходного значения.

⚠️ Не удаляйте исходную колонку, пока в списке проблемных строк остаются неразобранные. После Колонки.Удалить вернуть данные из неё нельзя.

Две частые ловушки:

  • Квалификаторы длины. Строка длиннее, чем разрешено в КвалификаторыСтроки, обрезается. Если ограничение не критично, ставьте Неограниченная длину или заведомо большее значение.
  • Квалификаторы числа. Число с большим количеством знаков после запятой округляется до заданной точности, а при переполнении разрядности значение не помещается.

Ускорение на больших таблицах

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

Значения = Таблица.ВыгрузитьКолонку(ИмяКолонки);

Для Индекс = 0 По Значения.ВГраница() Цикл
	Значения[Индекс] = НовоеОписание.ПривестиЗначение(Значения[Индекс]);
КонецЦикла;

Таблица.Колонки.Добавить(ВремИмя, НовоеОписание);
Таблица.ЗагрузитьКолонку(Значения, ВремИмя);
Таблица.Колонки.Удалить(ИмяКолонки);

Что это даёт:

  • 🔹 цикл идёт по обычному массиву, без поиска ячейки по имени на каждой итерации;
  • 🔹 вызов ЗагрузитьКолонку записывает значения одной операцией;
  • 🔹 размер массива совпадает с числом строк, так что соответствие позиций сохраняется.

Для очень больших выборок дополнительно:

  • не создавайте промежуточные таблицы там, где достаточно массива;
  • не вызывайте Таблица.Индекс(Строка) в цикле по большой таблице: это поиск строки в таблице на каждой итерации. Используйте счётчик;
  • приводите тип как можно раньше, ещё в запросе: ВЫРАЗИТЬ(Поле КАК Число(15, 2)). Тогда таблица значений уже получит правильный тип колонки, и менять ничего не придётся.

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

Колонки реквизита формы: отдельный случай

Если таблица значений — реквизит формы, а не просто переменная в модуле, колонки задаются в описании реквизита формы. Добавить или убрать колонку на лету можно только через метод формы ИзменитьРеквизиты.

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

  1. Выгрузите значения колонки в массив (ВыгрузитьКолонку у реквизита, приведённого к значению через РеквизитФормыВЗначение).
  2. Опишите добавляемый реквизит: имя, ОписаниеТипов, путь к таблице. Удаляемый реквизит укажите в списке удаляемых.
  3. Вызовите ИзменитьРеквизиты с обоими списками.
  4. Добавьте в форму элемент для новой колонки: после изменения реквизитов элементы не появляются сами.
  5. Загрузите сохранённые значения в новую колонку через ЗагрузитьКолонку.

Метод вызывается из модуля формы. Если реквизит определён в конфигураторе, а не создан динамически, тип его колонки меняется там же, в редакторе формы, в свойствах колонки реквизита. Динамическое изменение стоит применять, только когда структура зависит от данных.

Если после смены типа колонка в форме выглядит «пустой», проверьте привязку: элемент формы должен ссылаться на путь к данным новой колонки, а не старой. Это самая частая причина, по которой данные в таблице есть, а на экране их нет.

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

Проверка результата после смены типа

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

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

Сверку сумм делайте для числовых колонок: если исходная колонка строковая, сначала посчитайте сумму циклом, приведя строки к числу, и сравните с Итог по новой колонке.

Короткая памятка по выбору способа:

СитуацияЧто делать
Таблица из запросаПривести тип в запросе через ВЫРАЗИТЬ
Небольшая таблица, имя не важноНовая колонка, перенос, удаление старой
Нужны прежние имена и порядокПересоздать таблицу
Сотни тысяч строкВыгрузка колонки в массив, пакетная загрузка
Реквизит формыИзменитьРеквизиты и перенос значений
Источник «грязный» (строки из файла)Сначала очистка и разбор, потом приведение
Перед удалением старой колонки
Выполнено: 0 / 4

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

Можно ли просто присвоить колонке новый ТипЗначения?
Нет. Свойство доступно только для чтения, поэтому нужна новая колонка или новая таблица.
Что будет, если записать строку в числовую колонку?
Платформа приведёт значение к типу колонки. Если строка не похожа на число, в ячейке окажется 0, и ошибки не будет.
Как сделать колонку, принимающую любые типы?
Добавьте её без описания типов: Колонки.Добавить("Значение"). Тогда ограничений на ячейку нет.
Сохранится ли порядок колонок после добавления новой?
Новая колонка встаёт в конец. Если порядок важен, пересоздайте таблицу, добавляя колонки в нужной последовательности.
Как проверить, что значение подходит под тип колонки до записи?
Используйте метод СодержитТип у ОписаниеТипов на результате ТипЗнч(Значение). Он отвечает, разрешён ли такой тип в колонке.