Как загрузить таблицу значений с кодами в табличную часть документа с получением ссылок на справочники в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде «1С:Предприятие» программисты часто сталкиваются с задачей заполнения табличной части (ТЧ) документа данными из внешней таблицы значений (ТЗ). Типичный сценарий: исходная ТЗ получена из внешнего источника (например, загружена из файла Excel, CSV или получена через API) и вместо готовых ссылок на элементы базы данных содержит лишь строковые или числовые идентификаторы — коды номенклатуры, артикулы, штрихкоды или коды контрагентов.

Если попытаться загрузить такую ТЗ напрямую в ТЧ с помощью стандартного метода Загрузить(), то колонки со ссылочными типами данных останутся пустыми. В этой статье мы подробно разберем, почему так происходит, рассмотрим неэффективные подходы и детально изучим лучшие практики решения этой задачи с примерами кода.

Почему не работает стандартный метод ТЧ.Загрузить(ТЗ)?

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

  1. Имена колонок должны совпадать: Платформа сопоставляет колонки источника (ТЗ) и приемника (ТЧ) строго по их именам с учетом регистра. Если в ТЗ колонка называется КодНоменклатуры, а в ТЧ — Номенклатура, автоматического переноса данных не произойдет.
  2. Типы данных должны быть идентичными: Это ключевой момент. Если в ТЧ колонка имеет ссылочный тип, например, СправочникСсылка.Номенклатура, а в ТЗ в аналогичной колонке содержатся текстовые строки с кодами (например, "00-000015"), автоматического преобразования типов (неявного приведения строки к ссылке) платформа не выполняет. Вместо этого в ТЧ запишется пустая ссылка данного справочника.

Многие начинающие разработчики пытаются решить эту проблему «в лоб» — перебором строк в цикле и поиском элементов справочника по коду на каждой итерации с помощью метода НайтиПоКоду(). Давайте проанализируем, почему этот подход использовать не стоит.

Проблема поиска в цикле (проблема запросов N+1)

Рассмотрим пример неоптимального кода:


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

Почему это плохо? Каждый вызов метода НайтиПоКоду() инициирует отдельное обращение к базе данных (СУБД). Если в вашей таблице значений содержится 1000 строк, система выполнит 1000 отдельных SQL-запросов к СУБД. Это приводит к колоссальным потерям производительности, блокировкам таблиц и долгому ожиданию со стороны пользователя. В профессиональной разработке 1С такой подход считается грубейшей ошибкой.

Рассмотрим правильные, быстрые и оптимальные способы решения этой задачи.

Способ 1. Оптимальное заполнение через запрос с временной таблицей

Использование запроса к таблице значений — это наиболее быстрый и технологически правильный способ сопоставления кодов и получения ссылок. Мы передаем всю ТЗ в запрос за одно обращение, связываем её с реальной таблицей справочника в базе данных и получаем готовый результат.

Разберем этот процесс по шагам:

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

Посмотрим на практическую реализацию этого метода:


// Шаг 1. Готовим исходную ТЗ и строго типизируем ее колонки
ТЗ_СКодами = Новый ТаблицаЗначений;
// Обязательно указываем типы данных для колонок
ТЗ_СКодами.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(9)));
ТЗ_СКодами.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", , Новый КвалификаторыЧисла(15, 3)));

// Заполняем ТЗ данными (в реальной задаче это может быть чтение файла)
НоваяСтрока = ТЗ_СКодами.Добавить();
НоваяСтрока.Код = "00-000001";
НоваяСтрока.Количество = 10;

НоваяСтрока = ТЗ_СКодами.Добавить();
НоваяСтрока.Код = "00-000002";
НоваяСтрока.Количество = 5;

// Шаг 2. Создаем запрос для сопоставления кодов со ссылками
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Передаем ТЗ как параметр
Запрос.УстановитьПараметр("ВнешняяТЗ", ТЗ_СКодами);

// Помещаем ТЗ во временную таблицу и соединяем со справочником
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВнешняяТЗ.Код КАК Код,
    |	ВнешняяТЗ.Количество КАК Количество
    |ПОМЕСТИТЬ ВТ_Данные
    |ИЗ
    |	&ВнешняяТЗ КАК ВнешняяТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ЕСТЬNULL(СправочникНоменклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
    |	ВТ_Данные.Количество КАК Количество
    |ИЗ
    |	ВТ_Данные КАК ВТ_Данные
    |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
    |		ПО ВТ_Данные.Код = СправочникНоменклатура.Код";

// Шаг 3. Выполняем запрос и выгружаем результат в новую ТЗ
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();

// Шаг 4. Очищаем ТЧ и загружаем в нее готовые данные со ссылками
Объект.ТЧ.Очистить();
Объект.ТЧ.Загрузить(ТаблицаРезультат);

Этот способ работает невероятно быстро, поскольку вся обработка данных и сопоставление происходят непосредственно на уровне СУБД за один транзакционный цикл.

Способ 2. Получение ссылки по Уникальному Идентификатору (GUID)

Если под «идентификаторами» в вашей задаче понимаются не коды справочника (человекочитаемые строки вроде "00012"), а глобальные уникальные идентификаторы (GUID вида "110e8400-e29b-11d4-a716-446655440000"), то решить задачу можно без запросов к базе данных.

Для этого в платформе 1С предусмотрен метод менеджера справочника ПолучитьСсылка(). Он формирует ссылку в оперативной памяти компьютера на основе переданного GUID. Рассмотрим пример:


// Предположим, у нас есть ТЗ с колонкой "IDСтрока", где хранятся GUID элементов в виде текста
ТЗ_С_GUID = Новый ТаблицаЗначений;
ТЗ_С_GUID.Колонки.Добавить("IDСтрока", Новый ОписаниеТипов("Строка"));
ТЗ_С_GUID.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

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

Для Каждого СтрокаТЗ Из ТЗ_С_GUID Цикл
    Попытка
        // Преобразуем строковое представление GUID в объект УникальныйИдентификатор
        GUID_Объекта = Новый УникальныйИдентификатор(СтрокаТЗ.IDСтрока);
        // Получаем ссылку "на лету" без обращения к БД
        СтрокаТЗ.Номенклатура = Справочники.Номенклатура.ПолучитьСсылка(GUID_Объекта);
    Исключение
        // Обработка некорректного формата GUID
        СтрокаТЗ.Номенклатура = Справочники.Номенклатура.ПустаяСсылка();
    КонецПопытки;
КонецЦикла;

// Теперь колонки совпадают и ТЗ можно загрузить в ТЧ
Объект.ТЧ.Загрузить(ТЗ_С_GUID);

Важное предупреждение: Метод ПолучитьСсылка() генерирует ссылку физически в памяти. Он не проверяет, существует ли реально в вашей базе данных элемент с таким идентификатором. Если элемента с таким GUID в справочнике нет, ссылка всё равно запишется в табличную часть, но в интерфейсе пользователь увидит надпись «<Объект не найден>» (так называемая «битая ссылка»). Используйте этот способ только тогда, когда вы на 100% уверены, что GUID актуальны и получены из этой же базы данных (например, при обмене данными между идентичными конфигурациями).

Нюансы работы на Управляемых Формах (1С 8.3)

При разработке в интерфейсе «Такси» на управляемых формах следует помнить о разграничении контекстов выполнения кода (Клиент и Сервер). Таблица значений как тип данных физически не существует на тонком клиенте. Любые операции с таблицами значений и запросами должны выполняться строго в процедурах с директивой &НаСервере или &НаСервереБезКонтекста.

Также на управляемой форме табличная часть документа представлена типом ДанныеФормыКоллекция, а не объектом ТабличнаяЧасть. Хотя платформа позволяет вызывать метод Загрузить() на сервере формы напрямую у реквизита Объект.ТЧ, в сложных алгоритмах обмена рекомендуется использовать специализированные методы преобразования типов:


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

Применение метода ЗначениеВДанныеФормы() гарантирует корректное обновление интерфейса формы и снижает вероятность возникновения ошибок при интерактивной работе пользователя с документом после программного заполнения.

Альтернатива: Использование метода ЗаполнитьЗначенияСвойств

Если структуры вашей внешней таблицы значений и табличной части документа существенно различаются, и вам не нужно перезаписывать (очищать) всю табличную часть, а требуется лишь обновить определенные реквизиты или дописать строки выборочно, метод Загрузить() не подойдет, так как он полностью стирает прежние данные ТЧ.

В этом случае отличной альтернативой выступает метод ЗаполнитьЗначенияСвойств(), используемый при обходе строк:


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

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

Резюме и рекомендации

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

← На главную