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

· 9 мин

Содержание

Чтобы скопировать таблицу значений в другую таблицу значений в 1С 8.3, вызовите ТЗКопия = ТЗ.Скопировать(); — метод вернёт новую независимую таблицу с теми же колонками и строками. Нужны не все строки или не все колонки — передайте в Скопировать() массив строк или структуру отбора и список колонок через запятую. Нужна только пустая структура — используйте СкопироватьКолонки().

Частая ловушка: ТЗ2 = ТЗ1 ничего не копирует. Обе переменные смотрят на один и тот же объект, и правка в ТЗ2 тут же «появляется» в ТЗ1. Ниже — все рабочие способы переноса данных, с кодом, который можно вставить в модуль и запустить.

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

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

У таблицы три части, которые важно различать при копировании:

  • 📐 Колонки (ТЗ.Колонки) — имя, тип значения (ОписаниеТипов), заголовок, ширина.
  • 📄 Строки — элементы типа СтрокаТаблицыЗначений, к ним обращаются по индексу ТЗ[0] или в цикле Для Каждого.
  • 🔎 Индексы (ТЗ.Индексы) — ускоряют Найти() и НайтиСтроки().

Присваивание и копия — не одно и то же

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

ТЗ2 = ТЗ1;               // ссылка на тот же объект
ТЗ2[0].Сумма = 500;
Сообщить(ТЗ1[0].Сумма);  // 500 — изменился «оригинал»

ТЗ3 = ТЗ1.Скопировать(); // отдельный объект
ТЗ3[0].Сумма = 1;
Сообщить(ТЗ1[0].Сумма);  // по-прежнему 500

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

Метод Скопировать(): полная копия и копия по отбору

Скопировать(Строки, Колонки) — самый быстрый и короткий способ скопировать таблицу значений в другую. Он выполняется внутри платформы, без цикла на встроенном языке.

Оба параметра необязательные:

ПараметрЧто передатьРезультат
не указан—все строки и все колонки
СтрокиМассив строк исходной таблицытолько эти строки
СтрокиСтруктура отборастроки, где значения колонок равны значениям структуры
Колонкистрока "Имя1, Имя2"только перечисленные колонки

Полная копия

ТЗКопия = ТЗИсточник.Скопировать();

Копия с отбором по значению

Допустим, в таблице остатков 3 000 строк, а нужны только строки по одному складу и всего две колонки:

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

Копия выбранных строк

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

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

ТЗПоложительные = ТЗОстатки.Скопировать(МассивСтрок);

Массив для простого равенства удобно получить через НайтиСтроки(): ТЗ.Скопировать(ТЗ.НайтиСтроки(Отбор)) даёт тот же результат, что и передача структуры.

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

СкопироватьКолонки, ВыгрузитьКолонку и ЗагрузитьКолонку

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

Пустая таблица с той же структурой

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

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

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

Перенос одной колонки через массив

ВыгрузитьКолонку() отдаёт значения колонки массивом в порядке строк. ЗагрузитьКолонку() раскладывает массив по строкам приёмника.

// В приёмнике уже есть строки, добавляем к ним колонку цен
ТЗПриемник.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число",
    Новый КвалификаторыЧисла(15, 2)));

Цены = ТЗИсточник.ВыгрузитьКолонку("Цена");
ТЗПриемник.ЗагрузитьКолонку(Цены, "Цена");

ЗагрузитьКолонку() не добавляет строки: она заполняет только те, что уже есть, по порядку. Значит, строки в двух таблицах должны идти в одном порядке и соответствовать друг другу. Если источник отсортирован иначе, цена «уедет» на чужую номенклатуру, и ошибки при этом не будет.

Как подготовить приёмник под загрузку колонки:

  1. Создайте структуру: ТЗПриемник = ТЗИсточник.СкопироватьКолонки("Номенклатура").
  2. Добавьте нужное число пустых строк циклом Для Сч = 1 По ТЗИсточник.Количество() Цикл ТЗПриемник.Добавить(); КонецЦикла;.
  3. Загрузите каждую колонку: ТЗПриемник.ЗагрузитьКолонку(ТЗИсточник.ВыгрузитьКолонку("Номенклатура"), "Номенклатура").

На практике этот путь выбирают редко: для полной копии хватает Скопировать(). Зато ВыгрузитьКолонку() незаменим, когда из таблицы нужен просто список значений — например, массив ссылок для параметра запроса В (&Список).

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

Запрос нужен, когда при переносе данные надо сгруппировать, соединить с базой или отфильтровать сложным условием. Таблицу значений передают параметром и сначала помещают во временную таблицу — выбирать напрямую из параметра можно только в запросе с ПОМЕСТИТЬ.

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ", ТЗИсточник);
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
|    &ТЗ КАК Т
|;
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    ВТ.Номенклатура КАК Номенклатура,
|    СУММА(ВТ.Количество) КАК Количество
|ИЗ
|    ВТ_Данные КАК ВТ
|ГДЕ
|    ВТ.Количество > 0
|СГРУППИРОВАТЬ ПО
|    ВТ.Номенклатура";

ТЗРезультат = Запрос.Выполнить().Выгрузить();

Выгрузить() результата запроса сразу возвращает новую таблицу значений — отдельный цикл не нужен.

Обязательное условие: типизированные колонки

Если колонку создали без типа (ТЗ.Колонки.Добавить("Сумма")), запрос упадёт с ошибкой Тип не может быть выбран в запросе. Каждой колонке, которая попадает в запрос, задайте ОписаниеТипов:

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

Таблица, полученная из Выгрузить() другого запроса или из ТабличнаяЧасть.Выгрузить(), уже типизирована — с ней проблем нет.

Как собрать такой запрос в конструкторе

  1. В модуле в Конфигураторе установите курсор внутри строки текста запроса и вызовите конструктор из контекстного меню (Конструктор запроса...).
  2. На вкладке с таблицами создайте описание временной таблицы: задайте имя, например ВТ_Данные, и перечислите поля с типами — те же, что у колонок вашей таблицы.
  3. В тексте, который сформирует конструктор, замените источник на параметр &ТЗ и добавьте ПОМЕСТИТЬ.
  4. Добавьте второй запрос пакета, где из временной таблицы выбираете, группируете и фильтруете.

Временная таблица живёт до конца выполнения запроса. Если она нужна в нескольких разных запросах, создайте Новый МенеджерВременныхТаблиц и присвойте его свойству Запрос.МенеджерВременныхТаблиц каждого из них.

Объединение двух таблиц в одну

Склеить две таблицы с одинаковыми колонками удобно тем же приёмом: обе помещаются во временные таблицы, а итог собирается через ОБЪЕДИНИТЬ ВСЕ. Встроенного метода «добавить все строки другой таблицы» у таблицы значений нет.

Построчный перенос: цикл, ЗаполнитьЗначенияСвойств и выборка

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

ЗаполнитьЗначенияСвойств — перенос одноимённых колонок

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

Процедура копирует значения только тех колонок, имена которых совпадают в обеих таблицах. Лишние колонки источника игнорируются, недостающие в приёмнике остаются пустыми. Третий и четвёртый параметры ограничивают перенос: ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрИсточник, "Номенклатура, Цена") скопирует только две колонки, а параметр «исключая свойства» — все, кроме перечисленных.

Разные имена колонок и пересчёт

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

Проверим пример на числах: при количестве 4 и цене 125,50 в колонку Сумма попадёт 502.

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

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

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    НоваяСтрока = ТЗПриемник.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;

Здесь ЗаполнитьЗначенияСвойств берёт значения полей выборки по псевдонимам из запроса. Поэтому псевдонимы КАК ... должны совпадать с именами колонок приёмника.

Табличная часть документа и реквизит формы

Табличная часть объекта — не таблица значений, но конвертируется в неё в одну строку:

  • ТЗ = ДокументОбъект.Товары.Выгрузить(); — получить таблицу значений из табличной части;
  • ДокументОбъект.Товары.Загрузить(ТЗ); — полностью заменить строки табличной части данными таблицы (одноимённые колонки).

На управляемой форме код работы с таблицей значений пишут в серверной процедуре (&НаСервере): ТЗ = РеквизитФормыВЗначение("ИмяРеквизита"), обработка, затем ЗначениеВРеквизитФормы(ТЗ, "ИмяРеквизита").

Какой метод копирования выбрать: сравнение

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

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

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

Частые ошибки

  • 🔗 Присваивание вместо копии. ТЗ2 = ТЗ1 — одна таблица под двумя именами. Симптом: «сами собой» меняются строки в исходных данных.
  • 🧩 Нетипизированная колонка в запросе — ошибка Тип не может быть выбран в запросе. Лечится заданием ОписаниеТипов при Колонки.Добавить().
  • 🏷 Не совпадают имена колонок. ЗаполнитьЗначенияСвойств молча пропускает колонку с другим именем, а в приёмнике остаётся пустое значение. Сверьте имена или присвойте значение явно.
  • 🖥 Таблица значений в клиентском коде. На тонком клиенте объекта нет, будет ошибка о недоступности типа. Переносите обработку в процедуру &НаСервере.
  • 🗑 Удаление строк в прямом цикле. Для Каждого с ТЗ.Удалить() пропускает строки. Удаляйте с конца:
Для Сч = 1 По ТЗ.Количество() Цикл
    Стр = ТЗ[ТЗ.Количество() - Сч];
    Если Стр.Количество = 0 Тогда
        ТЗ.Удалить(Стр);
    КонецЕсли;
КонецЦикла;

Граница цикла вычисляется один раз при входе, а индекс считается от текущего размера. Для таблицы из 5 строк на первом шаге берётся ТЗ[4]; если её удалили, на втором шаге ТЗ[3] — последняя из оставшихся. Так каждая строка проверяется ровно один раз.

Как ускорить перенос больших таблиц

  1. ⚡ Предпочитайте платформенные методы циклам. Скопировать() и Выгрузить() результата запроса работают внутри платформы и на десятках тысяч строк заметно быстрее, чем цикл с Добавить().
  2. 🔎 Добавьте индекс перед поиском. Если в цикле переноса вы ищете соответствие во второй таблице через НайтиСтроки(), сначала выполните ТЗ.Индексы.Добавить("Номенклатура"). Без индекса каждый поиск перебирает всю таблицу.
  3. 📦 Копируйте только нужные колонки. Параметр Колонки в Скопировать() уменьшает объём памяти, особенно когда в источнике есть строковые колонки неограниченной длины.
  4. 🧮 Не гоняйте таблицу между клиентом и сервером. Каждый серверный вызов с большим реквизитом формы — передача данных по сети. Собирайте и обрабатывайте всё за один вызов &НаСервере.
  5. 🧹 Освобождайте память. После работы с крупной временной таблицей присвойте переменной Неопределено, если процедура продолжает работать дальше.

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

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