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

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

В процессе разработки в среде «1С:Предприятие» программисты регулярно сталкиваются с необходимостью сопоставить данные из двух разных источников. Типичный пример: у нас есть две таблицы значений — ТЗ_1 и ТЗ_2. Обе содержат колонки Сотрудник (строкового типа) и Должность. Наша задача — найти совпадения по имени сотрудника и перенести корректную должность из первой таблицы во вторую.

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

Метод №1. Простой вложенный цикл (двойной перебор)

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

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


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

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

Главный недостаток: крайне низкая производительность. Этот алгоритм имеет квадратичную сложность — O(N * M). Если в каждой таблице будет всего по 1000 строк, то системе придется выполнить до 1 000 000 операций сравнения! На реальных базах данных при объеме таблиц более пары сотен строк этот метод вызовет заметное зависание сеанса пользователя.

Метод №2. Оптимизация через объект «Соответствие» (Хеш-таблица)

Давайте значительно ускорим наш код. Вместо того чтобы каждый раз обходить вторую таблицу вложенным циклом, мы можем один раз прочитать ТЗ_1 и сохранить пары «Сотрудник - Должность» во внутреннем объекте платформы 1С — Соответствие.

Поиск в объекте Соответствие работает по принципу хеш-таблицы и выполняется практически мгновенно (за константное время O(1)), независимо от общего количества элементов в коллекции. Сложность всего нашего алгоритма снизится до линейной — O(N + M).

Разберем этот изящный способ по шагам:


// Шаг 1. Создаем и наполняем хэш-таблицу (кэш в оперативной памяти)
КэшДолжностей = Новый Соответствие;

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

// Шаг 2. Обходим вторую таблицу в один проход
Для Каждого Строка2 Из ТЗ_2 Цикл
    КлючПоиска = ВРег(СокрЛП(Строка2.Сотрудник));
    
    // Быстро извлекаем должность из кэша по ключу
    НайденнаяДолжность = КэшДолжностей.Получить(КлючПоиска);
    
    // Если соответствие найдено (значение не равно Неопределено)
    Если НайденнаяДолжность <> Неопределено Тогда
        Строка2.Должность = НайденнаяДолжность;
    КонецЕсли;
КонецЦикла;

Почему это работает быстрее? Мы избавились от вложенного цикла. Теперь вместо миллионов итераций система делает ровно столько шагов, сколько строк содержится в обеих таблицах суммарно. Этот метод рекомендуется использовать всегда, когда сопоставление данных производится непосредственно на стороне клиента или сервера приложений без обращения к СУБД.

Метод №3. Использование встроенных индексов Таблицы Значений

Платформа «1С:Предприятие» позволяет индексировать колонки таблиц значений. Индексация переводит поиск строк через метод НайтиСтроки() в режим бинарного поиска, что работает во много раз быстрее стандартного перебора.

Однако есть важный нюанс: индексы в 1С чувствительны к регистру символов и лишним пробелам. Чтобы решить задачу автора темы, нам придется сначала создать вспомогательную колонку с нормализованными данными, проиндексировать ее, а затем выполнить поиск.

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


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

Для Каждого Строка1 Из ТЗ_1 Цикл
    Строка1.СотрудникНорм = ВРег(СокрЛП(Строка1.Сотрудник));
КонецЦикла;

// Добавляем индекс по подготовленной колонке
ТЗ_1.Индексы.Добавить("СотрудникНорм");

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

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

Метод №4. Использование Запроса и Временных Таблиц

Когда речь идет об обработке больших массивов информации, самым правильным и профессиональным подходом в 1С является передача таблиц в менеджер временных таблиц и выполнение соединения на уровне СУБД. База данных сделает сопоставление на порядок быстрее любого кода на встроенном языке.

Для реализации этого метода необходимо помнить о двух строгих правилах платформы 1С:

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

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


// Шаг 1. Описываем типы для колонок (это обязательно для запроса!)
МассивТиповСтрока = Новый Массив;
МассивТиповСтрока.Добавить(Тип("Строка"));
ОписаниеСтроки = Новый ОписаниеТипов(МассивТиповСтрока, , Новый КвалификаторыСтроки(150));

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

ТЗ_2_Типизированная = Новый ТаблицаЗначений;
ТЗ_2_Типизированная.Колонки.Добавить("Сотрудник", ОписаниеСтроки);
ТЗ_2_Типизированная.Колонки.Добавить("Должность", ОписаниеСтроки);

// Заполняем новые таблицы данными из исходных ТЗ
Для Каждого Стр Из ТЗ_1 Цикл
    ЗаполнитьЗначенияСвойств(ТЗ_1_Типизированная.Добавить(), Стр);
КонецЦикла;

Для Каждого Стр Из ТЗ_2 Цикл
    ЗаполнитьЗначенияСвойств(ТЗ_2_Типизированная.Добавить(), Стр);
КонецЦикла;

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

// Передаем типизированные таблицы значений в параметры запроса
Запрос.УстановитьПараметр("ВнешняяТЗ_1", ТЗ_1_Типизированная);
Запрос.УстановитьПараметр("ВнешняяТЗ_2", ТЗ_2_Типизированная);

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

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

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

Резюме: какой метод выбрать?

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

← На главную