В процессе разработки в среде «1С:Предприятие» программисты регулярно сталкиваются с необходимостью сопоставить данные из двух разных источников. Типичный пример: у нас есть две таблицы значений — ТЗ_1 и ТЗ_2. Обе содержат колонки Сотрудник (строкового типа) и Должность. Наша задача — найти совпадения по имени сотрудника и перенести корректную должность из первой таблицы во вторую.
Решить эту задачу можно множеством способов. Выбор конкретного метода напрямую зависит от объема обрабатываемых данных и требований к производительности вашей системы. В этой статье мы подробно разберем четыре наиболее эффективных и популярных подхода: от простого перебора до использования быстрых хэш-таблиц (объекта Соответствие) и запросов СУБД.
Это самый очевидный алгоритм, который чаще всего приходит на ум начинающим разработчикам. Мы берем каждую строку из второй таблицы и во вложенном цикле ищем совпадение в первой таблице.
Рассмотрим пример реализации такого алгоритма с важными доработками. Обратите внимание на то, что при сравнении строковых полей в коде 1С крайне важно использовать функции очистки пробелов СокрЛП() и приведения к единому регистру ВРег() (или НРег()), так как по умолчанию сравнение строк в коде чувствительно к регистру символов.
Для Каждого Строка2 Из ТЗ_2 Цикл
// Очищаем пробелы и приводим имя сотрудника к верхнему регистру
СотрудникПоиск = ВРег(СокрЛП(Строка2.Сотрудник));
Для Каждого Строка1 Из ТЗ_1 Цикл
СотрудникИсточник = ВРег(СокрЛП(Строка1.Сотрудник));
// Если нашли полное совпадение по имени
Если СотрудникПоиск = СотрудникИсточник Тогда
Строка2.Должность = Строка1.Должность;
// Прерываем внутренний цикл, чтобы не делать лишних итераций
Прервать;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Преимущества метода: простота написания и чтения кода, не требуется предварительная подготовка таблиц значений.
Главный недостаток: крайне низкая производительность. Этот алгоритм имеет квадратичную сложность — O(N * M). Если в каждой таблице будет всего по 1000 строк, то системе придется выполнить до 1 000 000 операций сравнения! На реальных базах данных при объеме таблиц более пары сотен строк этот метод вызовет заметное зависание сеанса пользователя.
Давайте значительно ускорим наш код. Вместо того чтобы каждый раз обходить вторую таблицу вложенным циклом, мы можем один раз прочитать ТЗ_1 и сохранить пары «Сотрудник - Должность» во внутреннем объекте платформы 1С — Соответствие.
Поиск в объекте Соответствие работает по принципу хеш-таблицы и выполняется практически мгновенно (за константное время O(1)), независимо от общего количества элементов в коллекции. Сложность всего нашего алгоритма снизится до линейной — O(N + M).
Разберем этот изящный способ по шагам:
// Шаг 1. Создаем и наполняем хэш-таблицу (кэш в оперативной памяти)
КэшДолжностей = Новый Соответствие;
Для Каждого Строка1 Из ТЗ_1 Цикл
// Ключ обязательно нормализуем (убираем пробелы, приводим к верхнему регистру)
КлючПоиска = ВРег(СокрЛП(Строка1.Сотрудник));
// Записываем должность, соответствующую данному сотруднику
КэшДолжностей.Вставить(КлючПоиска, Строка1.Должность);
КонецЦикла;
// Шаг 2. Обходим вторую таблицу в один проход
Для Каждого Строка2 Из ТЗ_2 Цикл
КлючПоиска = ВРег(СокрЛП(Строка2.Сотрудник));
// Быстро извлекаем должность из кэша по ключу
НайденнаяДолжность = КэшДолжностей.Получить(КлючПоиска);
// Если соответствие найдено (значение не равно Неопределено)
Если НайденнаяДолжность <> Неопределено Тогда
Строка2.Должность = НайденнаяДолжность;
КонецЕсли;
КонецЦикла;
Почему это работает быстрее? Мы избавились от вложенного цикла. Теперь вместо миллионов итераций система делает ровно столько шагов, сколько строк содержится в обеих таблицах суммарно. Этот метод рекомендуется использовать всегда, когда сопоставление данных производится непосредственно на стороне клиента или сервера приложений без обращения к СУБД.
Платформа «1С:Предприятие» позволяет индексировать колонки таблиц значений. Индексация переводит поиск строк через метод НайтиСтроки() в режим бинарного поиска, что работает во много раз быстрее стандартного перебора.
Однако есть важный нюанс: индексы в 1С чувствительны к регистру символов и лишним пробелам. Чтобы решить задачу автора темы, нам придется сначала создать вспомогательную колонку с нормализованными данными, проиндексировать ее, а затем выполнить поиск.
Проанализируем реализацию этого подхода:
// Добавляем служебную колонку в ТЗ_1 для поиска без учета регистра
ТЗ_1.Колонки.Добавить("СотрудникНорм", Новый ОписаниеТипов("Строка"));
Для Каждого Строка1 Из ТЗ_1 Цикл
Строка1.СотрудникНорм = ВРег(СокрЛП(Строка1.Сотрудник));
КонецЦикла;
// Добавляем индекс по подготовленной колонке
ТЗ_1.Индексы.Добавить("СотрудникНорм");
// Выполняем поиск совпадений для ТЗ_2
Для Каждого Строка2 Из ТЗ_2 Цикл
КлючПоиска = ВРег(СокрЛП(Строка2.Сотрудник));
// Структура для поиска по индексу
ПараметрыОтбора = Новый Структура("СотрудникНорм", КлючПоиска);
НайденныеСтроки = ТЗ_1.НайтиСтроки(ПараметрыОтбора);
// Если строка найдена, берем должность из первой найденной строки
Если НайденныеСтроки.Количество() > 0 Тогда
Строка2.Должность = НайденныеСтроки[0].Должность;
КонецЕсли;
КонецЦикла;
Этот способ уступает использованию Соответствия по лаконичности кода и расходу памяти (так как мы модифицируем исходную таблицу), но отлично подходит, если вам в дальнейшем нужно делать многократные поиски по разным критериям в одной и той же таблице.
Когда речь идет об обработке больших массивов информации, самым правильным и профессиональным подходом в 1С является передача таблиц в менеджер временных таблиц и выполнение соединения на уровне СУБД. База данных сделает сопоставление на порядок быстрее любого кода на встроенном языке.
Для реализации этого метода необходимо помнить о двух строгих правилах платформы 1С:
ВРег() в запросе нет необходимости.Посмотрим на пример кода, реализующего сопоставление через запрос:
// Шаг 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 = Запрос.Выполнить().Выгрузить();
Когда использовать этот метод? Это идеальное решение, если вы работаете на стороне сервера и объем сравниваемых таблиц превышает несколько тысяч строк. База данных оптимизирует соединение физически на уровне дисков и оперативной памяти сервера СУБД.
Подведем итоги нашего анализа и выберем оптимальный инструмент под конкретные задачи:
ВРег() и СокрЛП().