Как дополнить таблицу значений в 1С: сопоставление колонок, оптимизация запросов и правильная работа с GUID

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

При разработке прикладных решений на платформе «1С:Предприятие 8» программисты регулярно сталкиваются с необходимостью обработки табличных данных в оперативной памяти. Одной из классических задач является наполнение существующей таблицы значений (ТЗ) новыми строками, полученными из запроса или внешнего источника. При этом разработчики часто совершают типичные ошибки: вручную перебирают десятки колонок, передают огромные массивы в параметры запросов или пытаются перехватить ошибки генерации ссылок по GUID с помощью неэффективных конструкций.

В этой статье мы подробно разберем, как правильно решать подобные задачи с использованием лучших практик разработки в 1С. Мы изучим альтернативные методы копирования данных при несовпадении колонок, рассмотрим перевод работы со списками на временные таблицы для повышения производительности и выясним, почему стандартный метод получения ссылки по GUID работает «лениво» и как это обойти.

1. Копирование данных при несовпадении имен колонок

Представим ситуацию: у нас есть заполненная таблица значений (Приемник), содержащая колонки Пользователь, Гуид и Организация. Из запроса или стороннего источника мы получаем данные (Источник), где аналогичные по смыслу поля называются иначе: ФизическоеЛицо, Guid1C и Филиал. Нам нужно дополнить исходную ТЗ новыми строками.

Стандартный метод ЗаполнитьЗначенияСвойств() в данном случае напрямую не сработает, так как имена колонок не совпадают. Построчное ручное присваивание в цикле — не лучший вариант, особенно если колонок много. Рассмотрим два оптимальных пути решения этой проблемы.

Способ А. Использование алиасов (псевдонимов) в запросе (Рекомендуемый)

Если данные-источники мы получаем с помощью запроса к базе данных, то самым производительным и лаконичным решением будет переименование полей на уровне СУБД. Мы просто назначаем полям запроса такие же псевдонимы (алиасы), какие используются в нашей таблице значений. В этом случае мы сможем применить быстрое заполнение свойств.

Давайте посмотрим на пример реализации такого подхода:


// Создаем запрос для получения новых данных
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Сотрудники.Ссылка КАК Пользователь,
    |	Сотрудники.УникальныйИдентификаторИБ КАК Гуид,
    |	Сотрудники.ГоловнаяОрганизация КАК Организация
    |ИЗ
    |	Справочник.Сотрудники КАК Сотрудники
    |ГДЕ
    |	Сотрудники.Активен";

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

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

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

Способ Б. Создание универсальной карты сопоставления (Маппинга)

Что делать, если изменить структуру запроса или источника невозможно (например, данные приходят из закрытого внешнего API или промежуточной библиотеки)? В этом случае мы можем написать универсальный механизм сопоставления полей с использованием структуры или соответствия. Это избавит нас от жесткого кодирования каждой строки.

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


// Определяем карту соответствия полей (Ключ - колонка Приемника, Значение - колонка Источника)
КартаСопоставления = Новый Соответствие;
КартаСопоставления.Вставить("Пользователь", "ФизическоеЛицо");
КартаСопоставления.Вставить("Гуид", "Guid1C");
КартаСопоставления.Вставить("Организация", "Филиал");

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

Такой подход делает код гибким и легко масштабируемым: при изменении структуры колонок вам достаточно будет скорректировать только правила в объекте Соответствие.

2. Оптимизация передачи списков в запросы: почему массив — это плохой выбор

Перейдем ко второй технической проблеме. Часто для получения данных по определенному списку пользователей разработчики сначала выгружают их уникальные идентификаторы или ссылки в массив, а затем передают его параметром в запрос: ГДЕ Ссылка В (&МассивУсловий).

Рассмотрим, почему этот подход таит в себе серьезные угрозы производительности:

  1. Ограничения СУБД: При передаче массива в параметр В (&Массив) платформа 1С преобразует его в SQL-конструкцию IN (значение1, значение2, ...). Если в массиве содержится более нескольких сотен или тысяч элементов, СУБД может построить крайне неэффективный план запроса или вернуть ошибку превышения лимита параметров.
  2. Избыточная нагрузка на память: Массив полностью загружается в оперативную память сервера приложений 1С и передается на СУБД в виде гигантской строки запроса, что нерационально расходует ресурсы.

Альтернативное профессиональное решение: Временные таблицы

Стандартом разработки на платформе «1С:Предприятие 8» при работе с большими объемами ограничений является использование временных таблиц. Мы можем передать всю таблицу значений напрямую в запрос в качестве внешнего источника.

Давайте разберем этот процесс по шагам:

  1. Колонки исходной таблицы значений обязательно должны быть жестко типизированы при ее создании. Без явного указания типов платформа не сможет сопоставить типы данных для создания полей во временной таблице SQL.
  2. В запросе мы инициализируем МенеджерВременныхТаблиц.
  3. Передаем таблицу значений как параметр и помещаем ее во временную таблицу.
  4. Выполняем ВНУТРЕННЕЕ СОЕДИНЕНИЕ нашей рабочей таблицы базы данных с созданной временной таблицей.

Рассмотрим детальный пример реализации:


// Шаг 1. Создаем типизированную Таблицу Значений для передачи в запрос
ТЗ_ДляЗапроса = Новый ТаблицаЗначений;

// Описываем типы для колонки ФизическоеЛицо
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("СправочникСсылка.ФизическиеЛица"));
ОписаниеТиповФЛ = Новый ОписаниеТипов(МассивТипов);

ТЗ_ДляЗапроса.Колонки.Добавить("ФизическоеЛицо", ОписаниеТиповФЛ);

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

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

// Передаем нашу ТЗ как параметр
Запрос.УстановитьПараметр("ВнешняяТаблицаФизическихЛиц", ТЗ_ДляЗапроса);

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

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

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

3. Развенчиваем мифы: ленивая ссылка ПолучитьСсылку() и обработка исключений

В сообщениях форума часто встречается попытка проверить существование физического лица в базе данных по его уникальному идентификатору (GUID) с помощью конструкции Попытка...Исключение:


Попытка
    Стр.ФизЛицо = Справочники.ФизическиеЛица.ПолучитьСсылку(Новый УникальныйИдентификатор(Стр.Guid1C));
Исключение
    Стр.ФизЛицо = Справочники.ФизическиеЛица.ПустаяСсылка();
КонецПопытки;

Почему этот код не работает и никогда не вызовет исключение?

Метод ПолучитьСсылку() у менеджеров справочников и документов является так называемым «ленивым» методом платформы. Он работает исключительно в оперативной памяти клиентского или серверного приложения. Его задача — просто сконструировать объект типа СправочникСсылка на основании переданной строки уникального идентификатора и метаданных.

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

Как правильно проверить существование ссылки в базе данных?

Существует два основных способа решения этой задачи. Разберем их особенности.

Способ 1. Проверка через быстрый запрос (Рекомендуемый для списков объектов)

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


// Сконструируем ссылку по GUID
СсылкаДляПроверки = Справочники.ФизическиеЛица.ПолучитьСсылку(Новый УникальныйИдентификатор(Стр.Guid1C));

// Выполним проверку наличия записи в базе данных
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ Первые 1
    |	ФизическиеЛица.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ФизическиеЛица КАК ФизическиеЛица
    |ГДЕ
    |	ФизическиеЛица.Ссылка = &Ссылка";

Запрос.УстановитьПараметр("Ссылка", СсылкаДляПроверки);
РезультатПроверки = Запрос.Выполнить();

Если РезультатПроверки.Пустой() Тогда
    // Объекта в базе данных нет, ссылка "битая"
    Стр.ФизЛицо = Справочники.ФизическиеЛица.ПустаяСсылка();
Иначе
    // Объект реально существует в базе данных
    Стр.ФизЛицо = СсылкаДляПроверки;
КонецЕсли;

Этот метод работает максимально оперативно, так как СУБД проверяет только наличие индекса по ссылке и не считывает тяжелые реквизиты объекта.

Способ 2. Проверка через ПолучитьОбъект() (Допустимо для единичных проверок)

Если вам нужно проверить только один объект и в случае успеха сразу начать с ним работу, можно использовать попытку получения объекта.


СсылкаДляПроверки = Справочники.ФизическиеЛица.ПолучитьСсылку(Новый УникальныйИдентификатор(Стр.Guid1C));
ОбъектБД = СсылкаДляПроверки.ПолучитьОбъект();

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

Обратите внимание, что метод ПолучитьОбъект() полностью считывает все реквизиты и табличные части элемента из базы данных в память сервера. Использовать этот способ в циклах при обработке больших массивов информации категорически не рекомендуется, так как это создаст избыточную нагрузку на дисковую подсистему и оперативную память сервера.

Выводы и рекомендации

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

← На главную