Перенос колонки между таблицами значений: цикл, массив, запрос
Содержание
Скопировать колонку из одной таблицы значений в другую можно тремя способами: циклом по строкам, парой ВыгрузитьКолонку и ЗагрузитьКолонку через массив или запросом с соединением по ключу. Цикл подходит, когда нужна проверка или преобразование. Массив быстрее всех, но требует одинакового порядка строк. Запрос выигрывает, когда строки двух таблиц связаны по ключу, а порядок разный.
Перед копированием проверьте два условия. Колонка должна существовать в таблице-приёмнике, иначе обращение к ней даст ошибку «Поле объекта не обнаружено». И нужно знать, как строки соотносятся между собой: по номеру или по значению ключевого поля. От этого зависит выбор способа.
Ниже везде используются две таблицы: ТЗИсточник (колонки Код, Номенклатура, Сумма) и ТЗПриёмник (колонки Код, Номенклатура). Нужно перенести Сумма.
Что нужно знать о колонках таблицы значений до копирования
Колонка таблицы значений — это отдельный объект в коллекции Колонки. У неё есть имя, тип значения, заголовок и ширина. Данные хранятся в строках, а не в колонке, поэтому «скопировать колонку» всегда означает два действия: создать колонку в приёмнике и заполнить значения по строкам.
Чтобы создать колонку, используйте метод Добавить коллекции Колонки:
ТЗПриёмник.Колонки.Добавить("Сумма", ТЗИсточник.Колонки.Сумма.ТипЗначения, "Сумма");
Тип берём прямо из источника — так не будет расхождений при последующем присваивании. Если нужно поставить колонку не в конец, а в определённое место, есть метод Вставить с индексом позиции.
Заранее проверьте, нет ли такой колонки в приёмнике:
Если ТЗПриёмник.Колонки.Найти("Сумма") = Неопределено Тогда
ТЗПриёмник.Колонки.Добавить("Сумма", ТЗИсточник.Колонки.Сумма.ТипЗначения);
КонецЕсли;
Если нужно перенести все колонки сразу вместе со структурой, не добавляйте их вручную. Метод СкопироватьКолонки() создаёт пустую таблицу с теми же колонками, а Скопировать(, "Код,Сумма") возвращает копию с выбранными колонками и данными.
Какой способ выбрать: сравнение
| Способ | Когда подходит | Ограничения |
|---|---|---|
| Цикл по строкам | Нужно преобразовать значение, проверить условие, строки сопоставляются по номеру | Медленнее массива на больших таблицах |
ВыгрузитьКолонку + ЗагрузитьКолонку | Таблицы одинаковой длины, порядок строк совпадает | Нет сопоставления по ключу, нет преобразований |
Найти / НайтиСтроки по ключу | Порядок разный, ключ уникален | Без индекса медленно на тысячах строк |
| Запрос к таблице значений | Нужно соединение по нескольким полям, большие объёмы | Типы колонок должны быть определены |
Начинайте с самого простого, который закрывает вашу задачу. Если порядок строк гарантирован, массив победит по скорости и по длине кода.
Классический способ: цикл по строкам
Когда строки идут в одном порядке, обращайтесь к ним по номеру:
Для Сч = 0 По ТЗИсточник.Количество() - 1 Цикл
ТЗПриёмник[Сч].Сумма = ТЗИсточник[Сч].Сумма;
КонецЦикла;
Пример сразу показывает слабое место: если в приёмнике строк меньше, чем в источнике, на последних итерациях будет ошибка выхода за границы индекса. Добавьте проверку длины до цикла:
Если ТЗИсточник.Количество() <> ТЗПриёмник.Количество() Тогда
ВызватьИсключение "Таблицы разной длины, копирование по номеру строки невозможно";
КонецЕсли;
Цикл удобен, когда значение нужно изменить по дороге. Например, округлить или отбросить пустые:
Для Сч = 0 По ТЗИсточник.Количество() - 1 Цикл
Значение = ТЗИсточник[Сч].Сумма;
Если Значение <> 0 Тогда
ТЗПриёмник[Сч].Сумма = Окр(Значение, 2);
КонецЕсли;
КонецЦикла;
Для обхода без номера работает и Для Каждого Строка Из ТЗИсточник Цикл, но тогда индекс строки придётся получать через ТЗИсточник.Индекс(Строка), что дороже. Если нужен номер, считайте обычным счётчиком.
Отдельный случай — копировать не всю колонку, а только часть строк. Например, перенести суммы только тех позиций, где сумма больше нуля. Здесь массив не подходит: он не умеет фильтровать. Берите цикл с условием или НайтиСтроки:
Отбор = Новый Структура("Код", "000123");
СтрокиИсточника = ТЗИсточник.НайтиСтроки(Отбор);
СтрокиПриёмника = ТЗПриёмник.НайтиСтроки(Отбор);
Если СтрокиИсточника.Количество() > 0 И СтрокиПриёмника.Количество() > 0 Тогда
СтрокиПриёмника[0].Сумма = СтрокиИсточника[0].Сумма;
КонецЕсли;
Так же поступают, когда колонку нужно перенести в таблицу с другой структурой: сначала приводят ключи к общему виду (например, обрезают пробелы через СокрЛП), потом ищут соответствие. Без этого Найти не увидит «000123 » и «000123» как одно значение.
Копирование через массив: ВыгрузитьКолонку и ЗагрузитьКолонку
Самый короткий способ — два вызова:
МассивСумм = ТЗИсточник.ВыгрузитьКолонку("Сумма");
ТЗПриёмник.ЗагрузитьКолонку(МассивСумм, "Сумма");
Метод ВыгрузитьКолонку возвращает массив значений колонки в порядке строк. ЗагрузитьКолонку кладёт эти значения в строки приёмника, начиная с первой. Колонка Сумма в приёмнике к этому моменту уже должна быть.
Строки сопоставляются только по позиции, ключ не проверяется. Если в источнике другой порядок или есть лишние строки, данные тихо разъедутся. Перед вызовом сравните количество строк, а при сомнениях в порядке отсортируйте обе таблицы методом Сортировать("Код").
Другой частый вариант — взять колонку одной таблицы и положить её в новую колонку с другим именем:
ТЗПриёмник.Колонки.Добавить("СуммаИзИсточника");
ТЗПриёмник.ЗагрузитьКолонку(ТЗИсточник.ВыгрузитьКолонку("Сумма"), "СуммаИзИсточника");
Так можно переносить и колонки с другим именем: в ЗагрузитьКолонку второй параметр — имя или индекс колонки-приёмника, а имя в источнике указывается при выгрузке.
Сопоставление по ключу: Найти и индексы
Порядок строк часто разный, но есть общий ключ — код, артикул, ИНН. Тогда копируют значение по найденной строке:
Для Каждого Строка Из ТЗПриёмник Цикл
Найденная = ТЗИсточник.Найти(Строка.Код, "Код");
Если Найденная <> Неопределено Тогда
Строка.Сумма = Найденная.Сумма;
КонецЕсли;
КонецЦикла;
Найти возвращает первую подходящую строку или Неопределено. Если ключ может повторяться, используйте НайтиСтроки(Структура): он вернёт массив строк, и придётся решить, какую брать или как складывать.
Без индекса каждый поиск перебирает таблицу целиком. При 5 000 строк в каждой это порядка 25 миллионов сравнений. Чтобы ускорить, добавьте индекс до цикла:
ТЗИсточник.Индексы.Добавить("Код");
После этого Найти и НайтиСтроки по колонке Код используют индекс. Для нескольких полей ключа указывайте их через запятую: "Код,Склад".
Ещё один приём: сначала собрать соответствие. Один проход по источнику, потом один по приёмнику, без повторных поисков:
Соответствие = Новый Соответствие;
Для Каждого Строка Из ТЗИсточник Цикл
Соответствие.Вставить(Строка.Код, Строка.Сумма);
КонецЦикла;
Для Каждого Строка Из ТЗПриёмник Цикл
Значение = Соответствие.Получить(Строка.Код);
Если Значение <> Неопределено Тогда
Строка.Сумма = Значение;
КонецЕсли;
КонецЦикла;
Здесь повторяющиеся ключи перезапишут друг друга: останется последнее значение. Если дубли возможны, сначала сверните источник методом Свернуть("Код", "Сумма").
Запрос к таблице значений
Если нужно объединить две таблицы по нескольким полям или вы уже работаете с запросом, передайте обе таблицы как параметры:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| П.Код КАК Код,
| П.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(И.Сумма, 0) КАК Сумма
|ИЗ
| &ТЗПриёмник КАК П
| ЛЕВОЕ СОЕДИНЕНИЕ &ТЗИсточник КАК И
| ПО П.Код = И.Код";
Запрос.УстановитьПараметр("ТЗПриёмник", ТЗПриёмник);
Запрос.УстановитьПараметр("ТЗИсточник", ТЗИсточник);
Результат = Запрос.Выполнить().Выгрузить();
На выходе получится новая таблица значений, где колонка Сумма уже подтянута. Приёмник менять не нужно, достаточно взять результат вместо него.
У способа есть условие: колонки параметров-таблиц должны иметь заданные типы. Если колонку создали без типа (Колонки.Добавить("Сумма")), запрос может отказаться работать с ней или вести себя непредсказуемо. Указывайте тип при создании.
Соединять таблицы по полям со значениями составного типа (строка и число в одной колонке) в запросе нельзя без приведения. Либо приводите через ВЫРАЗИТЬ, либо сначала нормализуйте значения в коде.
Типы данных и составные колонки
При переносе через ЗагрузитьКолонку и присваивание строк тип значения колонки-приёмника не меняет значение, но ограничивает его. Если у приёмника тип «Число», а в источнике строка, приведение выполнится на стороне платформы по правилам ТипЗначения.ПривестиЗначение: строка, не похожая на число, превратится в 0. Ошибки не будет, а данные потеряются.
Как застраховаться:
- Создавайте колонку-приёмник с типом источника:
ТЗИсточник.Колонки.Сумма.ТипЗначения. - Для составных колонок (число, строка, дата вместе) оставляйте тип пустым, тогда в колонку можно положить что угодно.
- Если нужно привести к другому типу, делайте это явно через
Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2))иПривестиЗначение.
Пример явного приведения при копировании в цикле:
ОписаниеЧисла = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
Для Сч = 0 По ТЗИсточник.Количество() - 1 Цикл
ТЗПриёмник[Сч].Сумма = ОписаниеЧисла.ПривестиЗначение(ТЗИсточник[Сч].Сумма);
КонецЦикла;
Квалификаторы тоже важны. Число с КвалификаторыЧисла(10, 0) отбросит дробную часть, и сумма 1250,75 станет 1251. Для денег берите минимум две цифры после запятой.
Скорость на больших таблицах
Для сотен строк разницы между способами не заметите. Для десятков тысяч картина другая. Ориентиры по порядку: массивный способ работает быстрее всех, цикл по номеру немного медленнее, поиск Найти без индекса может быть в десятки раз медленнее цикла. Точные цифры зависят от сервера, поэтому замерьте на своих данных через ТекущаяУниверсальнаяДатаВМиллисекундах() до и после.
Что реально помогает:
- 🚀 Использовать массив, когда порядок строк гарантирован.
- 🔑 Добавлять индексы на поля поиска перед циклом, а не внутри него.
- 📦 Не вызывать
Колонки.ДобавитьиКолонки.Найтивнутри цикла по строкам: структура меняется один раз до цикла. - ✂️ Передавать в запрос только нужные колонки, а не всю таблицу:
ТЗ.Скопировать(, "Код,Сумма"). - 🧮 Выносить обращения к свойствам за цикл:
Количество = ТЗ.Количество().
Если таблица строится на сервере, а копирование идёт на клиенте, лишних передач не допускайте. Каждый серверный вызов в цикле стоит дорого. Передайте таблицу на сервер целиком, обработайте и верните результат одним вызовом.
Отдельно про память. Таблица значений целиком лежит в оперативной памяти сеанса, а ВыгрузитьКолонку создаёт ещё и массив. Для таблицы в миллион строк это лишние десятки мегабайт. Если памяти мало, идите циклом без промежуточного массива либо обрабатывайте таблицу порциями.
Перед массовым копированием в рабочей базе прогоните код на небольшой выборке и сверьте итог: сумма колонки в источнике должна совпасть с суммой в приёмнике. Для этого удобен метод Итог("Сумма"): сравните ТЗИсточник.Итог("Сумма") и ТЗПриёмник.Итог("Сумма") после копирования. Расхождение сразу покажет потерянные строки.
ВыгрузитьКолонку и ЗагрузитьКолонку. Это самый короткий и быстрый вариант.Найти, либо соберите Соответствие одним проходом.Типичные ошибки при копировании колонки
- ❌
Поле объекта не обнаружено: в приёмнике нет колонки с таким именем. Проверьте регистр и опечатки, добавьте колонку до копирования. - ❌ Значения «съехали» на одну строку: перед
ЗагрузитьКолонкуодна из таблиц была отсортирована, а другая нет. - ❌ Нули вместо сумм: тип колонки-приёмника не позволяет принять значение, платформа приводит его молча. Сверьте типы и квалификаторы.
- ❌ Пустые значения у части строк: ключ найден не во всех строках источника. Это нормально для
Найти, просто задайте значение по умолчанию. - ❌ Изменили таблицу, пока шли по ней
Для Каждого: добавление и удаление строк в этом цикле ломает обход. Собирайте изменения отдельно и применяйте после. - ❌ Запрос не принимает таблицу-параметр: у одной из колонок не задан тип.
Изменение структуры (добавление и удаление колонок) во время обхода строк тоже не рекомендуется: делайте его до цикла.
Частые вопросы
Можно ли скопировать колонку вместе с данными одним вызовом?
Скопировать с перечнем колонок вернёт копию с данными. Для одной колонки в существующую таблицу — только связка ВыгрузитьКолонку и ЗагрузитьКолонку.Что будет, если в приёмнике строк больше, чем значений в массиве?
Как скопировать колонку с другим именем?
Работает ли копирование для табличной части документа?
Выгрузить() и дальше работать по описанным способам. Обратно загружают через Загрузить(ТЗ): колонки сопоставляются по именам.