Дублируем строки ТаблицыЗначений во встроенном языке 1С:Предприятия 8.3

· 10 мин

Содержание

Чтобы скопировать строку таблицы значений в 1С, создайте новую строку методом Добавить() или Вставить() и перенесите в неё значения одной командой ЗаполнитьЗначенияСвойств(НоваяСтрока, ИсходнаяСтрока). Отдельного метода «скопировать строку» у объекта ТаблицаЗначений нет: метод Скопировать() возвращает новую таблицу целиком, а не строку.

Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и в самописных решениях. Отдельно разобраны случаи, на которых чаще всего ошибаются: типизированные колонки, вложенные таблицы, копирование на управляемой форме и массовое копирование тысяч строк.

Базовый способ скопировать строку таблицы значений

Минимальный рабочий вариант — три строки кода. Новая строка встаёт в конец таблицы:

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

Здесь ИсходнаяСтрока — объект типа СтрокаТаблицыЗначений. Получить его можно несколькими путями:

  • 🔢 по индексу: ИсходнаяСтрока = ТЗ[0]; (нумерация с нуля);
  • 🔍 поиском по значению: ИсходнаяСтрока = ТЗ.Найти(Номенклатура, "Номенклатура");;
  • 📋 из массива найденных строк: ТЗ.НайтиСтроки(Новый Структура("Склад", Склад));
  • 🔁 в цикле Для Каждого Строка Из ТЗ Цикл.

Копия сразу под исходной строкой

Если дубль нужен не в конце, а рядом с оригиналом, используйте Вставить() с индексом на единицу больше:

Индекс = ТЗ.Индекс(ИсходнаяСтрока);
НоваяСтрока = ТЗ.Вставить(Индекс + 1);
ЗаполнитьЗначенияСвойств(НоваяСтрока, ИсходнаяСтрока);

Метод Индекс() возвращает позицию строки в таблице. Вставить(Индекс + 1) сдвигает все строки ниже на одну позицию вниз. Если исходная строка последняя, вставка с индексом, равным количеству строк, отработает как обычное добавление в конец.

Готовую строку можно передвинуть позже методом ТЗ.Сдвинуть(НоваяСтрока, -1): отрицательное смещение двигает строку вверх, положительное — вниз.

Почему не работает «НоваяСтрока = ИсходнаяСтрока»

Присваивание не копирует данные. Обе переменные начинают указывать на одну и ту же строку таблицы. Меняете НоваяСтрока.Количество — меняется и «оригинал», потому что это один объект. Новая строка в таблице при этом не появляется.

Копирование строки с заменой значений отдельных полей

На практике строку редко дублируют один в один. Обычно копию делают, чтобы поменять в ней одно-два поля: разбить количество на две партии, перенести остаток на другой склад, разделить сумму по статьям.

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

// Разбиваем строку на две: 30% уходит на второй склад
НоваяСтрока = ТЗ.Вставить(ТЗ.Индекс(ИсходнаяСтрока) + 1);
ЗаполнитьЗначенияСвойств(НоваяСтрока, ИсходнаяСтрока);

НоваяСтрока.Склад      = СкладРезерва;
НоваяСтрока.Количество = Окр(ИсходнаяСтрока.Количество * 0.3, 3);
НоваяСтрока.Сумма      = Окр(ИсходнаяСтрока.Сумма * 0.3, 2);

ИсходнаяСтрока.Количество = ИсходнаяСтрока.Количество - НоваяСтрока.Количество;
ИсходнаяСтрока.Сумма      = ИсходнаяСтрока.Сумма - НоваяСтрока.Сумма;

Проверим на числах. Было 10 шт. на 1 000 руб. В копию уйдёт 3 шт. и 300 руб., в оригинале останется 7 шт. и 700 руб. Итог по таблице не изменился: 10 шт. и 1 000 руб.

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

Копирование без части колонок

У ЗаполнитьЗначенияСвойств четыре параметра:

ПараметрЧто передатьПример
Приёмникстроку, в которую пишемНоваяСтрока
Источникстроку, из которой читаемИсходнаяСтрока
Список свойствимена через запятую — копировать только их"Номенклатура, Цена"
Исключая свойстваимена через запятую — копировать всё, кроме них"Количество, Сумма"

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

// Копия без количества и суммы: их заполнит пользователь
ЗаполнитьЗначенияСвойств(НоваяСтрока, ИсходнаяСтрока, , "Количество, Сумма");

Исключённые колонки в новой строке получат пустое значение своего типа. Подробнее о том, какое именно, — ниже, в разделе про типы.

Ручной перенос значений по колонкам

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

Обход по коллекции колонок:

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

К значению строки можно обращаться тремя способами:

  1. Через точку: Строка.Цена — когда имя колонки известно при написании кода.
  2. Через квадратные скобки с именем: Строка["Цена"] или Строка[Колонка.Имя] — когда имя приходит из переменной.
  3. Через квадратные скобки с индексом колонки: Строка[0] — работает, но код плохо читается и ломается при изменении порядка колонок.

Когда ручной обход оправдан

  • 🔄 Нужно на лету поменять значение: обрезать строку, пересчитать валюту, заменить пустую ссылку на значение по умолчанию.
  • 🧩 В исходной и целевой таблицах колонки называются по-разному, и есть соответствие имён.
  • 🧪 Нужно пропустить колонки по условию, например все, у которых имя начинается с Служебное.

Пример с соответствием имён колонок:

Соответствие = Новый Соответствие;
Соответствие.Вставить("Товар", "Номенклатура");
Соответствие.Вставить("Кол",   "Количество");

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

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

ЗаполнитьЗначенияСвойств между разными таблицами

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

Что произойдёт с несовпадающими колонками

  • ✅ Колонки, которые есть и в источнике, и в приёмнике, копируются.
  • ➖ Колонки, которых нет в приёмнике, просто не переносятся.
  • ⬜ Колонки приёмника, которых нет в источнике, остаются со значениями по умолчанию.
  • 🔤 Регистр букв в именах роли не играет: Цена и ЦЕНА для 1С одно и то же имя.

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

Перенос строки в табличную часть документа

Типичная задача: подобрали данные запросом в таблицу значений и переносите в табличную часть Товары:

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

НомерСтроки табличная часть проставит сама. Лишние колонки таблицы значений (например, служебный Остаток, по которому вы фильтровали) в документ не попадут, потому что в табличной части их нет.

Копирование в структуру и обратно

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

// Запомнить строку, удалить её и вернуть позже
Слепок = Новый Структура("Номенклатура, Количество, Цена, Сумма");
ЗаполнитьЗначенияСвойств(Слепок, ИсходнаяСтрока);

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

Пустые значения, типы колонок и вложенные таблицы

Скопированное значение зависит от того, задан ли у колонки тип. Это частая причина «странных» нулей и пустых строк после копирования.

Колонки с типом и без

При создании колонки тип задают третьим параметром не всегда. Сравните:

ТЗ.Колонки.Добавить("Комментарий");   // тип не задан
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число",
	Новый КвалификаторыЧисла(15, 3)));
КолонкаЗначение в новой пустой строкеЧто будет при записи значения другого типа
без типаНеопределенозапишется как есть
Число0значение приведётся к числу
Строка""значение приведётся к строке
Датапустая дата '00010101'значение приведётся к дате
ссылочный типпустая ссылка этого типазначение приведётся к типу колонки

Отсюда два практических следствия:

  1. Если в исходной таблице колонка без типа хранит Неопределено, а в приёмнике колонка Число, после копирования там будет 0. Проверка Значение = Неопределено в приёмнике больше не сработает.
  2. Квалификаторы тоже применяются. В колонке с квалификатором (15, 2) не сохранятся лишние знаки после запятой, а строка длиннее заданной длины будет обрезана.

Для проверки «значение не заполнено» используйте ЗначениеЗаполнено(НоваяСтрока.Склад). Функция корректно отвечает и для Неопределено, и для 0, и для пустой ссылки.

Ссылки копируются ссылками

Если в колонке лежит СправочникСсылка.Номенклатура, в новую строку попадёт та же ссылка. Это правильно: элемент справочника один, на него просто указывают две строки. Копию самого элемента справочника при этом никто не создаёт.

Вложенная таблица в ячейке — частая ловушка

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

Изменили вложенную таблицу в копии — изменилась и в оригинале. Чтобы этого не было, вложенную коллекцию копируют явно:

НоваяСтрока = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ИсходнаяСтрока);
Если ТипЗнч(ИсходнаяСтрока.Серии) = Тип("ТаблицаЗначений") Тогда
	НоваяСтрока.Серии = ИсходнаяСтрока.Серии.Скопировать();
КонецЕсли;

Для массива независимую копию создают через новый массив и цикл добавления элементов. Для структуры — через новую структуру и ЗаполнитьЗначенияСвойств после объявления ключей. При глубокой вложенности (таблица в таблице в таблице) копируйте каждый уровень.

Копирование строки на управляемой форме

На клиенте объекта ТаблицаЗначений нет. Реквизит формы такого типа на клиенте виден как ДанныеФормыКоллекция, а строки — как ДанныеФормыЭлементКоллекции. Хорошая новость: методы Добавить(), Вставить(), Индекс() и функция ЗаполнитьЗначенияСвойств у них есть, поэтому базовый код почти не меняется.

&НаКлиенте
Процедура ДублироватьСтроку(Команда)
	ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	Индекс = Товары.Индекс(ТекущиеДанные);
	НоваяСтрока = Товары.Вставить(Индекс + 1);
	ЗаполнитьЗначенияСвойств(НоваяСтрока, ТекущиеДанные);

	Элементы.Товары.ТекущаяСтрока = НоваяСтрока.ПолучитьИдентификатор();
КонецПроцедуры

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

Интерактивное копирование и очистка полей

Пользователь копирует строку в табличном поле клавишей F9 или командой копирования в командной панели таблицы. Программно то же действие запускает метод элемента формы Элементы.Товары.СкопироватьСтроку() — он открывает новую строку на редактирование, как будто пользователь нажал F9.

Чтобы при копировании очищались отдельные поля, используйте событие ПриНачалеРедактирования. У него есть параметры НоваяСтрока и Копирование:

&НаКлиенте
Процедура ТоварыПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
	Если НоваяСтрока И Копирование Тогда
		Элемент.ТекущиеДанные.Количество = 0;
		Элемент.ТекущиеДанные.Сумма = 0;
	КонецЕсли;
КонецПроцедуры

Запретить копирование совсем можно в обработчике ПередНачаломДобавления: если параметр Копирование равен Истина, установите Отказ = Истина.

Когда нужен сервер

Если над таблицей нужны методы, которых у коллекции формы нет (Скопировать(), Свернуть(), ВыгрузитьКолонку()), переходите на сервер и преобразуйте реквизит:

&НаСервере
Процедура ОбработатьНаСервере()
	ТЗ = РеквизитФормыВЗначение("Товары");
	// ... работа с ТаблицаЗначений
	ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры

После ЗначениеВРеквизитФормы строки получат новые идентификаторы, поэтому текущую строку на форме придётся установить заново.

Массовое копирование строк и скорость

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

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

Дублирование строк внутри той же таблицы

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

Надёжно — зафиксировать количество строк до цикла и идти по индексам:

ИсходноеКоличество = ТЗ.Количество();
Для Сч = 0 По ИсходноеКоличество - 1 Цикл
	НоваяСтрока = ТЗ.Добавить();
	ЗаполнитьЗначенияСвойств(НоваяСтрока, ТЗ[Сч]);
КонецЦикла;

Индексы исходных строк при добавлении в конец не меняются, так что ТЗ[Сч] всегда указывает на оригинал. Таблица из 500 строк станет таблицей из 1 000.

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

ИсходноеКоличество = ТЗ.Количество();
Для Сч = 1 По ИсходноеКоличество Цикл
	Индекс = ИсходноеКоличество - Сч;
	НоваяСтрока = ТЗ.Вставить(Индекс + 1);
	ЗаполнитьЗначенияСвойств(НоваяСтрока, ТЗ[Индекс]);
КонецЦикла;

Проверим на трёх строках A, B, C. Шаг 1: индекс 2, после C вставляется C′. Шаг 2: индекс 1, после B вставляется B′. Шаг 3: индекс 0, после A вставляется A′. Итог: A, A′, B, B′, C, C′. Строки выше текущей вставкой не затрагиваются, поэтому индексы считаются верно.

Что тормозит при больших объёмах

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

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

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