Перетипизация колонки ТаблицыЗначений: три рабочих способа

· 9 мин

Содержание

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

Ниже три способа сделать это в коде 1С 8.3: через дополнительную колонку, через новую таблицу и через запрос. Для каждого описано, что происходит со значениями, которые в новый тип не помещаются. Отдельно разобран случай, когда таблица лежит в реквизите управляемой формы.

Как устроена типизация колонок

Колонка получает тип в момент создания. Его задаёт второй параметр метода Колонки.Добавить():

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

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

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

  • строка "15" в числовую колонку превратится в число 15;
  • строка "abc" в числовую колонку превратится в 0, ошибки не будет;
  • число в строковую колонку станет строкой, длина которой обрезается по квалификатору;
  • значение, которое к типу не приводится, заменяется значением по умолчанию для этого типа.

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

Квалификаторы тоже часть типа. Колонка Число(10, 0) и колонка Число(15, 2) отличаются, и менять между ними разрядность нельзя точно так же, как и сам тип. Это же касается длины строки и состава даты.

Способ 1. Новая колонка рядом со старой

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

  1. Добавить колонку с временным именем и нужным типом.
  2. Пройти по строкам и скопировать значения из старой колонки в новую.
  3. Удалить старую колонку.
  4. Переименовать новую в имя старой.
Процедура ПеретипироватьКолонку(Таблица, ИмяКолонки, НовыйТип)

	ВременноеИмя = ИмяКолонки + "_Новая";
	Таблица.Колонки.Добавить(ВременноеИмя, НовыйТип);

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

	Таблица.Колонки.Удалить(ИмяКолонки);
	Таблица.Колонки[ВременноеИмя].Имя = ИмяКолонки;

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

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

ТипСумма = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ПеретипироватьКолонку(Таблица, "Сумма", ТипСумма);

Метод ПривестиЗначение() у ОписаниеТипов делает то же, что и присваивание в типизированную ячейку, но явно. Так в коде видно, где происходит приведение, и его можно заменить своей проверкой.

Что нужно учесть:

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

Способ 2. Новая таблица с правильными типами

Когда колонок несколько, а порядок и свойства важны, проще создать новую таблицу, описать в ней все колонки заново и перенести строки. Старую таблицу после этого можно отбросить.

Функция ПерестроитьТаблицу(Источник, Замены)

	// Замены — Соответствие: имя колонки -> новое ОписаниеТипов
	Результат = Новый ТаблицаЗначений;

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

	Для Каждого СтрокаИсточника Из Источник Цикл
		НоваяСтрока = Результат.Добавить();
		ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
	КонецЦикла;

	Возврат Результат;

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

Здесь ЗаполнитьЗначенияСвойств копирует значения по совпадающим именам, а приведение к новому типу платформа делает при записи в ячейки. Порядок колонок сохраняется, потому что они создаются в том же порядке, что в источнике.

Этот способ удобен ещё и тем, что исходную таблицу можно оставить нетронутой. Если что-то пошло не так, у вас остаётся оригинал для сравнения.

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

Для Каждого Колонка Из Источник.Колонки Цикл
	Результат.ЗагрузитьКолонку(Источник.ВыгрузитьКолонку(Колонка.Имя), Колонка.Имя);
КонецЦикла;

Перед этим в Результат надо создать нужное число пустых строк, иначе загружать будет некуда.

Способ 3. Запрос с приведением типов

Если нужно не просто сменить тип, а заодно отфильтровать, сгруппировать или объединить данные, используйте запрос, который берёт таблицу значений как источник. Типы приводятся прямо в тексте запроса оператором ВЫРАЗИТЬ:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Т.Наименование КАК Наименование,
|	ВЫРАЗИТЬ(Т.Сумма КАК ЧИСЛО(15, 2)) КАК Сумма
|ИЗ
|	&Таблица КАК Т";
Запрос.УстановитьПараметр("Таблица", Таблица);

НоваяТаблица = Запрос.Выполнить().Выгрузить();

Результат выгрузки уже имеет колонки с нужными типами. Исходная таблица не меняется.

Если таблицу надо использовать в нескольких запросах, поместите её во временную: создайте МенеджерВременныхТаблиц, выполните запрос с ПОМЕСТИТЬ и обращайтесь к временной таблице по имени. Это тот же приём, только результат живёт между запросами.

Ограничения способа:

  • запрос не сохраняет порядок строк без явного УПОРЯДОЧИТЬ ПО;
  • колонка, в которой лежат значения разных типов, не всегда приводится одним ВЫРАЗИТЬ;
  • значения, не помещающиеся в тип, дают NULL или обрезаются, поэтому результат полезно прогнать через ЕСТЬNULL.

Таблица на форме: когда нельзя просто пересоздать

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

Общая схема такая:

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

Изменение реквизитов формы убирает привязку элементов к удалённым реквизитам, поэтому поле на форме надо создавать заново и привязывать к Таблица.НоваяКолонка.

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

Замена через промежуточный массив вместо буфера обмена

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

Значения = Таблица.ВыгрузитьКолонку("Сумма");   // массив значений
Таблица.Колонки.Удалить("Сумма");
Таблица.Колонки.Добавить("Сумма", ТипСумма);
Таблица.ЗагрузитьКолонку(Значения, "Сумма");

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

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

Что происходит с данными при конвертации

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

ОткудаКудаРезультат
Строка "125,5"ЧислоЗависит от формата строки; проверьте на своих данных
Строка "abc"Число0
Число 12,345Число(10, 2)12,35: округление до двух знаков
Число 123456Число(4, 0)Значение не помещается в разрядность, результат не равен исходному
Длинная строкаСтрока(10)Обрезается до 10 символов
ЧислоСтрокаСтроковое представление
СтрокаДатаДата, если строка разбирается; иначе пустая дата

Строки со словом «зависит» надёжнее проверить на небольшом наборе: правила разбора зависят от формата строки.

Проверка до записи

Не надо полагаться на тихое приведение там, где потеря значения недопустима. Проверьте значение явно и сообщите о проблеме:

Если Не ЗначениеЗаполнено(Строка.Сумма)
	И ЗначениеЗаполнено(СтарыеЗначения[Индекс]) Тогда
	Сообщить("Не удалось привести значение в строке " + (Индекс + 1));
КонецЕсли;

Идея простая: если после приведения значение пустое, а до него было заполнено, строку нужно показать пользователю или записать в журнал. Для числа 0 это правило не подходит, поэтому для числовых колонок сравнивайте конкретные значения.

Приведение через Попытку

Когда нужно получить ошибку, а не ноль, используйте явное преобразование:

Попытка
	Сумма = Число(СтрЗаменить(Значение, " ", ""));
Исключение
	Сообщить("Не число: " + Значение);
	Сумма = 0;
КонецПопытки;

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

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

Короткое правило: смотрите на размер таблицы и на то, нужен ли порядок колонок.

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

Перед массовой перетипизацией прогоните способ на копии таблицы из 5-10 строк с «неудобными» значениями: пустыми, отрицательными, с запятой в числе, с пробелами. Так вы увидите, как именно платформа приводит ваши данные, и не будете гадать по таблице выше. Если результат устроил, запускайте на полном объёме.

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

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

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

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