В процессе разработки в среде 1С:Предприятие программисты часто сталкиваются с задачей интеграции данных из разных источников. Типичный пример: мы получаем две таблицы значений из разных функций (например, кадровые данные сотрудников и контактную информацию физических лиц) и нам необходимо слить их в одну общую таблицу. При этом связующим звеном выступает общий ключ — например, ссылка на элемент справочника ФизическоеЛицо.
В этой статье мы подробно разберем, как правильно решить эту задачу. Мы детально проанализируем распространенные ошибки, которые часто совершают разработчики на форумах, и рассмотрим два наиболее эффективных и отказоустойчивых метода: горизонтальное соединение с помощью механизма запросов и высокопроизводительное программное сопоставление через объект Соответствие.
Прежде чем перейти к правильным алгоритмам, давайте проанализируем ошибки, которые часто встречаются в советах на технических форумах. Понимание этих архитектурных просчетов убережет вас от трудноловимых багов в реальных базах данных.
Ошибка №1: Некорректный цикл добавления строк
В простых обсуждениях часто предлагают код следующего вида:
СтрокаИтоговойТЗ = тзИтог.Добавить();
Для каждого стр из КадровыеДанныеСотрудников
ЗаполнитьЗначенияСвойств(СтрокаИтоговойТЗ, стр);
КонецЦикла;
В данном фрагменте содержатся сразу две критические ошибки:
Добавить() вызывается один раз вне цикла. Это означает, что в итерациях цикла система будет бесконечно перезаписывать свойства одной и той же (единственной) строки итоговой таблицы. На выходе мы получим таблицу ровно из одной строки, содержащей данные последнего обработанного элемента.Добавить() внутрь циклов, мы получим простое последовательное добавление строк (вертикальное объединение). Данные по конкретному сотруднику из первой и второй таблиц не свяжутся в рамках одной строки, а запишутся в разные строки друг за другом.Ошибка №2: Использование ОБЪЕДИНИТЬ ВСЕ в запросе вместо СОЕДИНЕНИЯ
Еще одна частая ошибка — попытка использовать оператор ОБЪЕДИНИТЬ ВСЕ (UNION ALL) в тексте запроса при работе с временными таблицами. Оператор объединения просто «складывает» строки двух таблиц по вертикали (одна таблица под другой). Для того чтобы дополнить существующую строку сотрудника его персональными данными (телефон, адрес, паспорт), необходимо выполнять именно горизонтальное соединение (ЛЕВОЕ СОЕДИНЕНИЕ или ПОЛНОЕ СОЕДИНЕНИЕ) по общему ключевому полю.
---
Если вы выполняете код на стороне сервера, наиболее надежным и лаконичным способом объединения таблиц является использование механизма запросов платформы 1С. Мы помещаем обе исходные таблицы во временные таблицы пакета запроса, а затем соединяем их по ключевому полю.
Перед тем как передать таблицы значений в качестве параметров запроса, важно помнить об одном критическом правиле платформы: все колонки исходных таблиц значений, которые будут использоваться в запросе (особенно те, по которым происходит соединение), должны быть строго типизированы. Если колонка имеет тип Произвольный, система выдаст неустранимую ошибку выполнения: «Тип не может быть выбран в запросе».
Давайте разберем готовый шаблон кода для соединения таблиц через запрос:
// Создаем запрос и менеджер временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Формируем текст запроса с левым соединением
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ_Сотр.ФизическоеЛицо КАК ФизическоеЛицо,
| ТЗ_Сотр.Подразделение КАК Подразделение,
| ТЗ_Сотр.Должность КАК Должность
|ПОМЕСТИТЬ ВТ_Сотрудники
|ИЗ
| &КадровыеДанныеСотрудников КАК ТЗ_Сотр
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЗ_ФЛ.ФизическоеЛицо КАК ФизическоеЛицо,
| ТЗ_ФЛ.Адрес КАК Адрес,
| ТЗ_ФЛ.Телефон КАК Телефон
|ПОМЕСТИТЬ ВТ_ФизЛица
|ИЗ
| &КадровыеДанныеФЛ КАК ТЗ_ФЛ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Сотрудники.ФизическоеЛицо КАК ФизическоеЛицо,
| ВТ_Сотрудники.Подразделение,
| ВТ_Сотрудники.Должность,
| ВТ_ФизЛица.Адрес,
| ВТ_ФизЛица.Телефон
|ИЗ
| ВТ_Сотрудники КАК ВТ_Сотрудники
| // Связываем данные по ключевому полю ФизическоеЛицо
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ФизЛица КАК ВТ_ФизЛица
| ПО ВТ_Сотрудники.ФизическоеЛицо = ВТ_ФизЛица.ФизическоеЛицо";
// Передаем таблицы в параметры запроса
Запрос.УстановитьПараметр("КадровыеДанныеСотрудников", КадровыеДанныеСотрудников);
Запрос.УстановитьПараметр("КадровыеДанныеФЛ", КадровыеДанныеФЛ);
// Выполняем запрос и выгружаем результат в итоговую ТЗ
тзИтог = Запрос.Выполнить().Выгрузить();
В этом примере мы использовали ЛЕВОЕ СОЕДИНЕНИЕ. Это гарантирует, что в итоговую таблицу попадут абсолютно все сотрудники из первой таблицы, даже если для них не будут найдены сопоставленные физические лица во второй таблице (в этом случае поля адреса и телефона останутся пустыми, равными Null). Если вам нужны только те строки, которые гарантированно присутствуют в обеих таблицах, замените тип соединения на ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
---
Бывают ситуации, когда использование запроса нежелательно или невозможно (например, алгоритм работает в тонком клиенте без контекстного перехода на сервер, либо структуры таблиц динамические и их сложно описывать в запросе). В таких случаях программисты часто прибегают к поиску строк в цикле с помощью метода ТЗ.Найти().
Важное предупреждение: Использование метода Найти() или вложенных циклов перебора строк дает квадратичную сложность алгоритма $O(N^2)$. Если в ваших таблицах будет более 1000 строк, такой код начнет критически замедлять работу системы.
Чтобы выполнить программное соединение максимально быстро (за линейное время $O(N)$), мы применим встроенный объект платформы Соответствие. Поиск элемента по ключу в объекте Соответствие происходит практически мгновенно благодаря механизму хеширования.
Рассмотрим детальный алгоритм реализации этого метода:
// ШАГ 1. Готовим структуру итоговой таблицы.
// Копируем первую таблицу (получаем структуру и данные сотрудников)
тзИтог = КадровыеДанныеСотрудников.Скопировать();
// Добавляем в итоговую таблицу уникальные колонки из второй таблицы (Физлица)
Для Каждого Колонка Из КадровыеДанныеФЛ.Колонки Цикл
Если тзИтог.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
тзИтог.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЕсли;
КонецЦикла;
// ШАГ 2. Создаем "индекс" для быстрого поиска строк по ключу ФизическоеЛицо
КартаСтрок = Новый Соответствие;
Для Каждого СтрокаИтог Из тзИтог Цикл
// Ключ - Физлицо, Значение - ссылка на строку в памяти итоговой таблицы
КартаСтрок.Вставить(СтрокаИтог.ФизическоеЛицо, СтрокаИтог);
КонецЦикла;
// ШАГ 3. Быстро дополняем строки данными из второй таблицы
Для Каждого СтрокаФЛ Из КадровыеДанныеФЛ Цикл
// Ищем строку в итоговой таблице через наш индекс за O(1)
НайденнаяСтрокаИтог = КартаСтрок.Получить(СтрокаФЛ.ФизическоеЛицо);
Если НайденнаяСтрокаИтог <> Неопределено Тогда
// Заполняем только те колонки, которые пришли из таблицы Физлиц
ЗаполнитьЗначенияСвойств(НайденнаяСтрокаИтог, СтрокаФЛ);
КонецЕсли;
КонецЦикла;
Этот способ программного соединения работает чрезвычайно быстро и стабильно масштабируется на таблицы объемом в десятки тысяч строк, не создавая при этом лишней нагрузки на СУБД.
---
Если перед вами стоит задача не горизонтального соединения по ключу, а обычного слияния (добавления всех строк одной таблицы в конец другой), вам не нужно писать циклы вручную. В составе Библиотеки Стандартных Подсистем (БСП) есть готовая оптимизированная функция.
Мы можем использовать метод ДополнитьТаблицу из общего модуля БСП:
// Метод просто копирует все строки из источника в приемник, сопоставляя имена колонок
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(КадровыеДанныеФЛ, тзИтог);
Обратите внимание, что этот метод выполняет именно вертикальное объединение строк. Строки из первой и второй таблиц останутся разделенными, метод не осуществляет сопоставление по ключу ФизическоеЛицо.
---
Давайте подведем итоги и определим, в каких ситуациях какой метод предпочтительнее использовать: