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

· 10 мин

Содержание

Чтобы скопировать колонки таблицы значений в 1С, чаще всего хватает одной строки: НоваяТаблица = Таблица.Скопировать(, "Номенклатура, Количество");. Получится новая таблица со всеми строками, но только с перечисленными колонками. Если нужна пустая таблица с той же структурой, без строк, используйте Таблица.СкопироватьКолонки().

Частое заблуждение — что колонку можно «перетащить» в другую таблицу одним присваиванием, например Таблица2.Колонки.Добавить(Таблица1.Колонки.Цена). Так не работает : колонка всегда принадлежит своей таблице, а в другой таблице её нужно создать заново и заполнить значениями. Ниже — все рабочие способы с кодом для платформы 8.3, от встроенных методов до ручного переноса с переименованием и сменой типа.

С чего начать: что именно вы копируете

Под «копированием колонок» в 1С обычно понимают одну из четырёх задач. От неё зависит, какой метод брать.

  • 📐 Только структура. Нужна пустая таблица с теми же колонками, их типами и заголовками. Строки не нужны.
  • 📋 Часть колонок вместе с данными. Из большой выборки нужно оставить три-четыре поля.
  • 🔢 Значения одной колонки. Нужен массив значений — для запроса В (&Список), для проверки или для переноса в другую таблицу.
  • 🔁 Колонка с изменениями. То же поле, но под другим именем, с другим типом или в другой таблице с иным набором строк.

Ещё одно условие, о котором забывают: таблица значений существует только на сервере и в толстом клиенте. В тонком и веб-клиенте объявить переменную типа ТаблицаЗначений нельзя. Поэтому код копирования пишите в процедурах с директивой &НаСервере или в общем модуле с флагом «Сервер», а данные формы предварительно преобразуйте через РеквизитФормыВЗначение().

ЗадачаМетодЧто вернёт
Пустая копия структурыСкопироватьКолонки()Таблица значений без строк
Часть колонок с даннымиСкопировать(, "Колонки")Таблица значений со строками
Часть строк и колонокСкопировать(Отбор, "Колонки")Таблица значений с отобранными строками
Значения одной колонкиВыгрузитьКолонку()Массив
Перенос с новым именем или типомКолонки.Добавить() + циклИзменённая таблица

Встроенные методы: Скопировать и СкопироватьКолонки

Скопировать() с перечнем колонок

У метода Скопировать два необязательных параметра: строки и колонки. Колонки передаются строкой с именами через запятую. Пробелы после запятых допустимы.

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

// Вся таблица целиком
ПолнаяКопия = ТаблицаТоваров.Скопировать();

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

Скопировать() с отбором строк

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

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

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

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

Положительные = ТаблицаТоваров.Скопировать(ПодходящиеСтроки, "Номенклатура, Количество");

Колонку, по которой идёт отбор, не обязательно включать в результат. В примере выше отбор по Склад, а в новую таблицу склад не попадает.

СкопироватьКолонки() — пустой шаблон

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

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

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

Параметром можно передать строку с именами колонок — тогда в шаблон попадут только они: ТаблицаТоваров.СкопироватьКолонки("Номенклатура, Цена").

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

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

Шаг 1. Повторить структуру колонок

Процедура ДобавитьКолонкиИзТаблицы(Приемник, Источник, ИменаКолонок) Экспорт

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

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

Проверка через Колонки.Найти() защищает от повторного добавления: колонку с уже существующим именем платформа не создаст и выдаст исключение.

Шаг 2. Перенести значения

Здесь важно, как строки приёмника соотносятся со строками источника.

Строки идут один к одному. Например, приёмник получен из источника через Скопировать() и порядок не менялся. Тогда переносим по индексу:

Для Индекс = 0 По Источник.Количество() - 1 Цикл
    Приемник[Индекс].Цена = Источник[Индекс].Цена;
КонецЦикла;

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

Источник.Индексы.Добавить("Номенклатура");

Для Каждого СтрокаПриемника Из Приемник Цикл
    СтрокаИсточника = Источник.Найти(СтрокаПриемника.Номенклатура, "Номенклатура");
    Если СтрокаИсточника <> Неопределено Тогда
        СтрокаПриемника.Цена = СтрокаИсточника.Цена;
    КонецЕсли;
КонецЦикла;

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

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

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

Выгрузить колонку в массив и загрузить обратно

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

СписокНоменклатуры = ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура");

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

Парный метод — ЗагрузитьКолонку(Массив, Колонка). Он записывает элементы массива в колонку построчно: первый элемент в первую строку, второй во вторую и так далее. Новые строки при этом не создаются.

Связка двух методов — самый короткий способ продублировать колонку внутри одной таблицы:

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

Тот же приём годится для переноса между двумя таблицами с одинаковым числом и порядком строк:

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

Перед загрузкой сверьте количество строк: Источник.Количество() = Приемник.Количество(). При разной длине данные съедут, и ошибки вы не увидите — просто цены окажутся не у тех товаров.

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

У табличной части документа есть свой метод ВыгрузитьКолонку() и метод Выгрузить(), который тоже принимает список колонок: Объект.Товары.Выгрузить(, "Номенклатура, Количество"). Так вы сразу получите таблицу значений только с нужными полями, без промежуточного копирования.

Копирование колонки под другим именем или с другим типом

Переименование

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

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

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

Смена типа

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

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

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

ПривестиЗначение() не выдаёт исключение на неподходящих данных: если строка не превращается в число, в колонку попадёт пустое значение типа, то есть 0. Если ноль — допустимое значение, проверяйте исходную строку отдельно, иначе ошибочные данные спрячутся среди настоящих нулей.

Замена старой колонки новой на том же месте

Когда новая колонка должна полностью заменить старую, удаляем старую, переименовываем новую и сдвигаем её на прежнюю позицию:

ИндексСтарой = Таблица.Колонки.Индекс(Таблица.Колонки.СуммаСтрокой);
Таблица.Колонки.Удалить("СуммаСтрокой");

НоваяКолонка = Таблица.Колонки.СуммаЧисло;
НоваяКолонка.Имя = "Сумма";
Таблица.Колонки.Сдвинуть(НоваяКолонка, ИндексСтарой - Таблица.Колонки.Индекс(НоваяКолонка));

Метод Сдвинуть() принимает смещение относительно текущей позиции: отрицательное число двигает колонку влево. Если новая колонка стоит пятой (индекс 4), а старая была второй (индекс 1), смещение равно 1 − 4 = −3.

Какой способ копирования вам подходит?

Как ускорить работу с большими таблицами

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

  • ⚡ Встроенный метод быстрее цикла. Скопировать(), ВыгрузитьКолонку() и ЗагрузитьКолонку() выполняются внутри платформы. Цикл на встроенном языке с обращением к каждой строке заметно медленнее на тех же объёмах.
  • 🗂 Отбирайте колонки в запросе, а не после. Если таблица получена через Результат.Выгрузить(), лишние поля лучше вовсе не выбирать в тексте запроса. Копирование «лишнего» с последующим отсечением — двойная работа.
  • 🔍 Индекс перед поиском в цикле. Найти() и НайтиСтроки() без индекса перебирают таблицу целиком. Внутри цикла по второй таблице это даёт квадратичный рост времени. Индексы.Добавить("Ключ") один раз перед циклом убирает проблему.
  • 📌 Колонку получайте один раз. Не вызывайте Колонки.Найти() внутри цикла по строкам — найдите колонку заранее и сохраните в переменную.
  • 🧹 Освобождайте память. Временные копии больших таблиц занимают память рабочего процесса. Когда копия больше не нужна, присвойте переменной Неопределено.
  • 🔄 Меньше передач клиент — сервер. Копирование делайте в одном серверном вызове. Многократные РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы() в цикле — частая причина медленной формы.

Пример, где всё это сходится. Нужно добавить к 50 000 строк продаж закупочную цену из прайса на 8 000 позиций:

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

Для Каждого Строка Из Продажи Цикл
    НайденнаяСтрока = Прайс.Найти(Строка.Номенклатура, "Номенклатура");
    Если НайденнаяСтрока <> Неопределено Тогда
        Строка.ЦенаЗакупки = НайденнаяСтрока.Цена;
    КонецЕсли;
КонецЦикла;

Без строки с индексом каждый из 50 000 вызовов Найти() перебирает до 8 000 строк прайса. С индексом поиск идёт по индексу, и время обработки сокращается в разы.

Если обе таблицы пришли из базы, сопоставление по ключу проще отдать запросу: поместить одну таблицу во временную через параметр и соединить с другой ЛЕВЫМ СОЕДИНЕНИЕМ. На больших объёмах СУБД справится с этим лучше, чем цикл.

Ошибки при копировании колонок и как их найти

Диагностика: выберите симптом

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

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

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