В процессе разработки в среде «1С:Предприятие» программисты часто сталкиваются с задачей заполнения табличной части (ТЧ) документа данными из внешней таблицы значений (ТЗ). Типичный сценарий: исходная ТЗ получена из внешнего источника (например, загружена из файла Excel, CSV или получена через API) и вместо готовых ссылок на элементы базы данных содержит лишь строковые или числовые идентификаторы — коды номенклатуры, артикулы, штрихкоды или коды контрагентов.
Если попытаться загрузить такую ТЗ напрямую в ТЧ с помощью стандартного метода Загрузить(), то колонки со ссылочными типами данных останутся пустыми. В этой статье мы подробно разберем, почему так происходит, рассмотрим неэффективные подходы и детально изучим лучшие практики решения этой задачи с примерами кода.
Прежде чем перейти к решениям, выясним причину такого поведения платформы. Метод Загрузить() предназначен для быстрого копирования данных из одной таблицы в другую, но он работает по строгим правилам сопоставления типов и имен колонок:
КодНоменклатуры, а в ТЧ — Номенклатура, автоматического переноса данных не произойдет.СправочникСсылка.Номенклатура, а в ТЗ в аналогичной колонке содержатся текстовые строки с кодами (например, "00-000015"), автоматического преобразования типов (неявного приведения строки к ссылке) платформа не выполняет. Вместо этого в ТЧ запишется пустая ссылка данного справочника.Многие начинающие разработчики пытаются решить эту проблему «в лоб» — перебором строк в цикле и поиском элементов справочника по коду на каждой итерации с помощью метода НайтиПоКоду(). Давайте проанализируем, почему этот подход использовать не стоит.
Рассмотрим пример неоптимального кода:
Для Каждого СтрокаТЗ Из ТЗ Цикл
НоваяСтрока = Объект.ТЧ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
// Крайне неоптимальный поиск в цикле!
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоКоду(СтрокаТЗ.Код);
Если НЕ НайденнаяСсылка.Пустая() Тогда
НоваяСтрока.Номенклатура = НайденнаяСсылка;
КонецЕсли;
КонецЦикла;
Почему это плохо? Каждый вызов метода НайтиПоКоду() инициирует отдельное обращение к базе данных (СУБД). Если в вашей таблице значений содержится 1000 строк, система выполнит 1000 отдельных SQL-запросов к СУБД. Это приводит к колоссальным потерям производительности, блокировкам таблиц и долгому ожиданию со стороны пользователя. В профессиональной разработке 1С такой подход считается грубейшей ошибкой.
Рассмотрим правильные, быстрые и оптимальные способы решения этой задачи.
Использование запроса к таблице значений — это наиболее быстрый и технологически правильный способ сопоставления кодов и получения ссылок. Мы передаем всю ТЗ в запрос за одно обращение, связываем её с реальной таблицей справочника в базе данных и получаем готовый результат.
Разберем этот процесс по шагам:
Произвольный), запрос вернет ошибку «Тип не может быть выбран в запросе».ЛЕВОЕ СОЕДИНЕНИЕ связываем её со справочником по полю кода.Посмотрим на практическую реализацию этого метода:
// Шаг 1. Готовим исходную ТЗ и строго типизируем ее колонки
ТЗ_СКодами = Новый ТаблицаЗначений;
// Обязательно указываем типы данных для колонок
ТЗ_СКодами.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(9)));
ТЗ_СКодами.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", , Новый КвалификаторыЧисла(15, 3)));
// Заполняем ТЗ данными (в реальной задаче это может быть чтение файла)
НоваяСтрока = ТЗ_СКодами.Добавить();
НоваяСтрока.Код = "00-000001";
НоваяСтрока.Количество = 10;
НоваяСтрока = ТЗ_СКодами.Добавить();
НоваяСтрока.Код = "00-000002";
НоваяСтрока.Количество = 5;
// Шаг 2. Создаем запрос для сопоставления кодов со ссылками
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Передаем ТЗ как параметр
Запрос.УстановитьПараметр("ВнешняяТЗ", ТЗ_СКодами);
// Помещаем ТЗ во временную таблицу и соединяем со справочником
Запрос.Текст =
"ВЫБРАТЬ
| ВнешняяТЗ.Код КАК Код,
| ВнешняяТЗ.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ВнешняяТЗ КАК ВнешняяТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЕСТЬNULL(СправочникНоменклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
| ВТ_Данные.Количество КАК Количество
|ИЗ
| ВТ_Данные КАК ВТ_Данные
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
| ПО ВТ_Данные.Код = СправочникНоменклатура.Код";
// Шаг 3. Выполняем запрос и выгружаем результат в новую ТЗ
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
// Шаг 4. Очищаем ТЧ и загружаем в нее готовые данные со ссылками
Объект.ТЧ.Очистить();
Объект.ТЧ.Загрузить(ТаблицаРезультат);
Этот способ работает невероятно быстро, поскольку вся обработка данных и сопоставление происходят непосредственно на уровне СУБД за один транзакционный цикл.
Если под «идентификаторами» в вашей задаче понимаются не коды справочника (человекочитаемые строки вроде "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 актуальны и получены из этой же базы данных (например, при обмене данными между идентичными конфигурациями).
При разработке в интерфейсе «Такси» на управляемых формах следует помнить о разграничении контекстов выполнения кода (Клиент и Сервер). Таблица значений как тип данных физически не существует на тонком клиенте. Любые операции с таблицами значений и запросами должны выполняться строго в процедурах с директивой &НаСервере или &НаСервереБезКонтекста.
Также на управляемой форме табличная часть документа представлена типом ДанныеФормыКоллекция, а не объектом ТабличнаяЧасть. Хотя платформа позволяет вызывать метод Загрузить() на сервере формы напрямую у реквизита Объект.ТЧ, в сложных алгоритмах обмена рекомендуется использовать специализированные методы преобразования типов:
&НаСервере
Процедура ЗагрузитьТЗНаСервере(ДанныеВнешнейТаблицы)
// Преобразуем ДанныеФормыКоллекция в реальную ТаблицуЗначений для работы алгоритмов
ТаблицаДляОбработки = РеквизитФормыВЗначение("Объект.ТЧ", Тип("ТаблицаЗначений"));
// Выполняем алгоритмы заполнения, поиска ссылок запросом или сопоставления
// ... (код заполнения ТаблицаДляОбработки) ...
// Возвращаем результат обратно в реквизит формы
ЗначениеВДанныеФормы(ТаблицаДляОбработки, Объект.ТЧ);
КонецПроцедуры
Применение метода ЗначениеВДанныеФормы() гарантирует корректное обновление интерфейса формы и снижает вероятность возникновения ошибок при интерактивной работе пользователя с документом после программного заполнения.
Если структуры вашей внешней таблицы значений и табличной части документа существенно различаются, и вам не нужно перезаписывать (очищать) всю табличную часть, а требуется лишь обновить определенные реквизиты или дописать строки выборочно, метод Загрузить() не подойдет, так как он полностью стирает прежние данные ТЧ.
В этом случае отличной альтернативой выступает метод ЗаполнитьЗначенияСвойств(), используемый при обходе строк:
// Обходим строки подготовленной ТЗ (где коды уже заменены на ссылки)
Для Каждого СтрокаИсточника Из ТЗ_СГотовымиСсылками Цикл
// Добавляем новую строку в ТЧ документа
НоваяСтрокаТЧ = Объект.ТЧ.Добавить();
// Копируем данные только тех колонок, имена которых совпадают
ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрокаИсточника);
// Здесь можно вручную дозаполнить специфические поля, если требуется
НоваяСтрокаТЧ.ДатаДобавления = ТекущаяДата();
КонецЦикла;
Этот способ обеспечивает максимальную гибкость программирования, позволяя совмещать автоматическое сопоставление полей с ручной постобработкой данных в цикле.
Для успешной и быстрой загрузки данных из таблицы значений в табличную часть документа придерживайтесь следующих правил:
НайтиПоКоду()), если строк в таблице больше нескольких штук.ЗначениеВДанныеФормы().