Как объединить две таблицы значений по общей колонке в 1С

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

В практике разработки на платформе 1С нередко возникает задача обогащения одной таблицы значений данными из другой. Типичный сценарий: у нас есть таблица-приемник и таблица-источник, содержащие общее ключевое поле (например, Номенклатура или Договор), при этом количество строк в них не совпадает. Требуется сопоставить строки по совпадающей колонке и перенести недостающие данные. Разберем наиболее эффективные и надежные способы реализации этой задачи.

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

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

Проанализируем пример кода на встроенном языке 1С:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ *
    |ПОМЕСТИТЬ ВтПриемник
    |ИЗ &ТзПриемник КАК ТзПриемник
    |;
    |
    |ВЫБРАТЬ *
    |ПОМЕСТИТЬ ВтИсточник
    |ИЗ &ТзИсточник КАК ТзИсточник
    |;
    |
    |ВЫБРАТЬ
    |    ВтПриемник.*,
    |    ЕСТЬNULL(ВтИсточник.НужнаяКолонка, """") КАК ДополнительныеДанные
    |ИЗ
    |    ВтПриемник КАК ВтПриемник
    |    ЛЕВОЕ СОЕДИНЕНИЕ ВтИсточник КАК ВтИсточник
    |        ПО ВтПриемник.ОбщееПоле = ВтИсточник.ОбщееПоле";

Запрос.УстановитьПараметр("ТзПриемник", ТзПриемник);
Запрос.УстановитьПараметр("ТзИсточник", ТзИсточник);

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

Разберем по шагам важные нюансы этого подхода:

  1. Строгая типизация колонок: чтобы передать таблицу значений в запрос, все задействованные колонки должны иметь явно заданный тип данных через ОписаниеТипов. Таблицы с произвольными типами колонок вызовут исключение при вызове метода Выполнить().
  2. Выбор типа соединения: если применить ВНУТРЕННЕЕ СОЕДИНЕНИЕ, строки таблицы-приемника, которым не нашлось соответствия в источнике, будут безвозвратно потеряны. Чтобы сохранить все исходные строки приемника, используем ЛЕВОЕ СОЕДИНЕНИЕ с функцией ЕСТЬNULL() для обработки пустых значений.
  3. Типы строк: поля с типом «Строка» неограниченной длины нельзя использовать в секции ПО. Если общее поле строковое, ограничим его длину с помощью ВЫРАЗИТЬ(... КАК СТРОКА(N)).
  4. Дубли в источнике: если в ключевой колонке источника значения повторяются, левое соединение размножит строки приемника. В такой ситуации перед соединением таблицу-источник необходимо свернуть по ключевому полю.

Способ 2. Использование объекта Соответствие (хэш-таблица в памяти)

Посмотрим на альтернативный способ, который работает как на сервере, так и на клиенте без передачи данных в СУБД. Использование объекта Соответствие обеспечивает линейную вычислительную сложность и высокую скорость при больших объемах данных.

Рассмотрим реализацию алгоритма:


// Добавляем новую колонку в приемник, если ее еще нет
Если ТзПриемник.Колонки.Найти("НужнаяКолонка") = Неопределено Тогда
    ТзПриемник.Колонки.Добавить("НужнаяКолонка");
КонецЕсли;

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

// Обходим приемник и подставляем данные
Для Каждого СтрПриемник Из ТзПриемник Цикл
    ЗначениеИзИсточника = КэшДанных[СтрПриемник.ОбщееПоле];
    Если ЗначениеИзИсточника <> Неопределено Тогда
        СтрПриемник.НужнаяКолонка = ЗначениеИзИсточника;
    КонецЕсли;
КонецЦикла;

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

Способ 3. Поиск по индексированной таблице значений

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

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


// Добавляем индекс для быстрого поиска
ТзИсточник.Индексы.Добавить("ОбщееПоле");

// Создаем недостающие колонки в приемнике
Если ТзПриемник.Колонки.Найти("НужнаяКолонка") = Неопределено Тогда
    ТзПриемник.Колонки.Добавить("НужнаяКолонка");
КонецЕсли;

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

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

← На главную