В практике разработки на платформе 1С нередко возникает задача обогащения одной таблицы значений данными из другой. Типичный сценарий: у нас есть таблица-приемник и таблица-источник, содержащие общее ключевое поле (например, Номенклатура или Договор), при этом количество строк в них не совпадает. Требуется сопоставить строки по совпадающей колонке и перенести недостающие данные. Разберем наиболее эффективные и надежные способы реализации этой задачи.
Рассмотрим классический и наиболее наглядный вариант, предложенный в обсуждении: передача обеих таблиц в объект Запрос и их объединение с помощью оператора соединения. Этот подход идеально подходит, когда обработка данных происходит на стороне сервера.
Проанализируем пример кода на встроенном языке 1С:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ *
|ПОМЕСТИТЬ ВтПриемник
|ИЗ &ТзПриемник КАК ТзПриемник
|;
|
|ВЫБРАТЬ *
|ПОМЕСТИТЬ ВтИсточник
|ИЗ &ТзИсточник КАК ТзИсточник
|;
|
|ВЫБРАТЬ
| ВтПриемник.*,
| ЕСТЬNULL(ВтИсточник.НужнаяКолонка, """") КАК ДополнительныеДанные
|ИЗ
| ВтПриемник КАК ВтПриемник
| ЛЕВОЕ СОЕДИНЕНИЕ ВтИсточник КАК ВтИсточник
| ПО ВтПриемник.ОбщееПоле = ВтИсточник.ОбщееПоле";
Запрос.УстановитьПараметр("ТзПриемник", ТзПриемник);
Запрос.УстановитьПараметр("ТзИсточник", ТзИсточник);
РезультатТаблица = Запрос.Выполнить().Выгрузить();
Разберем по шагам важные нюансы этого подхода:
ОписаниеТипов. Таблицы с произвольными типами колонок вызовут исключение при вызове метода Выполнить().ВНУТРЕННЕЕ СОЕДИНЕНИЕ, строки таблицы-приемника, которым не нашлось соответствия в источнике, будут безвозвратно потеряны. Чтобы сохранить все исходные строки приемника, используем ЛЕВОЕ СОЕДИНЕНИЕ с функцией ЕСТЬNULL() для обработки пустых значений.ПО. Если общее поле строковое, ограничим его длину с помощью ВЫРАЗИТЬ(... КАК СТРОКА(N)).Посмотрим на альтернативный способ, который работает как на сервере, так и на клиенте без передачи данных в СУБД. Использование объекта Соответствие обеспечивает линейную вычислительную сложность и высокую скорость при больших объемах данных.
Рассмотрим реализацию алгоритма:
// Добавляем новую колонку в приемник, если ее еще нет
Если ТзПриемник.Колонки.Найти("НужнаяКолонка") = Неопределено Тогда
ТзПриемник.Колонки.Добавить("НужнаяКолонка");
КонецЕсли;
// Заполняем соответствие значениями источника
КэшДанных = Новый Соответствие;
Для Каждого СтрИсточник Из ТзИсточник Цикл
КэшДанных.Вставить(СтрИсточник.ОбщееПоле, СтрИсточник.НужнаяКолонка);
КонецЦикла;
// Обходим приемник и подставляем данные
Для Каждого СтрПриемник Из ТзПриемник Цикл
ЗначениеИзИсточника = КэшДанных[СтрПриемник.ОбщееПоле];
Если ЗначениеИзИсточника <> Неопределено Тогда
СтрПриемник.НужнаяКолонка = ЗначениеИзИсточника;
КонецЕсли;
КонецЦикла;
Этот способ выигрывает за счет того, что не требует типизации колонок и избавляет от накладных расходов на сериализацию данных и выполнение запроса в базе данных.
Если требуется перенести сразу несколько колонок, можно использовать штатные методы поиска Найти() или НайтиСтроки() в сочетании с процедурой ЗаполнитьЗначенияСвойств(). Выясним причину, почему разработчики иногда сталкиваются с зависанием этого метода, и как этого избежать.
По умолчанию поиск по строкам выполняет полный перебор всей таблицы. На таблицах от нескольких тысяч строк это приводит к резкому падению производительности. Чтобы поиск работал мгновенно, обязательно создадим индекс по ключевой колонке источника:
// Добавляем индекс для быстрого поиска
ТзИсточник.Индексы.Добавить("ОбщееПоле");
// Создаем недостающие колонки в приемнике
Если ТзПриемник.Колонки.Найти("НужнаяКолонка") = Неопределено Тогда
ТзПриемник.Колонки.Добавить("НужнаяКолонка");
КонецЕсли;
Отбор = Новый Структура("ОбщееПоле");
Для Каждого СтрПриемник Из ТзПриемник Цикл
Отбор.ОбщееПоле = СтрПриемник.ОбщееПоле;
НайденныеСтроки = ТзИсточник.НайтиСтроки(Отбор);
Если НайденныеСтроки.Количество() > 0 Тогда
// Копируем нужные свойства из первой найденной строки
ЗаполнитьЗначенияСвойств(СтрПриемник, НайденныеСтроки[0], "НужнаяКолонка");
КонецЕсли;
КонецЦикла;
Резюме: для сложных выборок с агрегацией и преобразованием типов на сервере предпочтителен вариант с запросом. Для простой связки данных на клиенте или в процедурах без обращения к СУБД оптимально использовать Соответствие либо поиск по предварительно индексированной таблице.