Как объединить две таблицы значений и добавить колонку из одной таблицы к другой

Программист IT и автоматизация бизнеса
← На главную

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

Постановка задачи

Представим ситуацию: у нас есть ПерваяТЗ с остатками на конец июля и ВтораяТЗ с остатками на конец июня. В обеих таблицах ключевым полем является ФИО (или ссылка на справочник Сотрудники). Нам нужно получить третью таблицу, где для каждого сотрудника будут выведены оба показателя в разных колонках. При этом важно учитывать, что состав сотрудников в таблицах может не совпадать: кто-то мог уволиться, а кто-то — только устроиться на работу.

Способ №1: Использование запроса с полным соединением (Рекомендуемый)

Самый быстрый и правильный способ с точки зрения производительности — это использование механизма временных таблиц в запросе. Этот метод позволяет платформе оптимизировать сопоставление данных на уровне сервера.

Для начала проанализируем, как правильно передать таблицы значений в запрос. Каждая таблица должна быть типизирована. Если колонки не имеют описания типов, запрос может отработать некорректно или вызвать ошибку. Рассмотрим код подготовки и выполнения запроса:


// Предположим, ПерваяТЗ и ВтораяТЗ уже заполнены данными
Запрос = Новый Запрос;

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

Запрос.УстановитьПараметр("ПерваяТЗ", ПерваяТЗ);
Запрос.УстановитьПараметр("ВтораяТЗ", ВтораяТЗ);

ИтоговаяТЗ = Запрос.Выполнить().Выгрузить();

Важные нюансы этого решения:

  1. ПОЛНОЕ СОЕДИНЕНИЕ: Мы используем именно полное соединение, так как сотрудники могут присутствовать только в первой или только во второй таблице. Если использовать ЛЕВОЕ СОЕДИНЕНИЕ, мы потеряем тех, кто был во второй таблице, но отсутствует в первой.
  2. Функция ЕСТЬNULL: Это критически важный момент. При полном соединении в полях ключевой колонки (в нашем случае Сотрудник) могут возникнуть значения NULL, если соответствие не найдено. Функция ЕСТЬNULL(ВТ_ОстаткиТекущие.Сотрудник, ВТ_ОстаткиПрошлые.Сотрудник) гарантирует, что мы получим непустую ссылку из той таблицы, где она есть.
  3. Типизация параметров: Чтобы запрос принял таблицу значений в качестве параметра &ПерваяТЗ, у этой таблицы должны быть четко заданы типы колонок при ее создании через Новый ОписаниеТипов.

Способ №2: Объединение с последующей группировкой

Иногда логика задачи требует не соединения, а «наслоения» данных. В этом случае мы можем использовать конструкцию ОБЪЕДИНИТЬ ВСЕ, после чего сгруппировать результат по ключевому полю.

Разберем этот алгоритм. Мы создаем одну общую выборку, где в каждой части объединения оставляем пустой (нулевой) ту колонку, которая заполняется в другой части. Посмотрим на пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ТЗ1.ФИО КАК Сотрудник,
|	ТЗ1.Остаток КАК Остаток1,
|	0 КАК Остаток2
|ПОМЕСТИТЬ ВТ_Общая
|ИЗ
|	&ПерваяТЗ КАК ТЗ1
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
|	ТЗ2.ФИО,
|	0,
|	ТЗ2.Остаток
|ИЗ
|	&ВтораяТЗ КАК ТЗ2
|;
|
|ВЫБРАТЬ
|	ВТ_Общая.Сотрудник,
|	СУММА(ВТ_Общая.Остаток1) КАК ОстатокИюль,
|	СУММА(ВТ_Общая.Остаток2) КАК ОстатокИюнь
|ИЗ
|	ВТ_Общая КАК ВТ_Общая
|СГРУППИРОВАТЬ ПО
|	ВТ_Общая.Сотрудник";

Запрос.УстановитьПараметр("ПерваяТЗ", ПерваяТЗ);
Запрос.УстановитьПараметр("ВтораяТЗ", ВтораяТЗ);

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

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

Способ №3: Программный обход с использованием индекса (без запроса)

Если объем данных невелик (до нескольких тысяч строк) или по каким-то причинам использование запроса невозможно (например, на клиенте в тонком клиенте без контекстного перехода на сервер), можно воспользоваться методом Найти(). Однако, чтобы это не превратилось в перебор «каждой строки с каждой» (что даст квадратичную сложность O(n²)), необходимо использовать индексацию.

Проанализируем шаги программного решения:

  1. Добавляем в ПервуюТЗ новую колонку для данных из второй таблицы.
  2. Для ВторойТЗ создаем индекс по ключевому полю.
  3. Обходим ПервуюТЗ циклом и ищем соответствие во второй.

// 1. Добавляем колонку в основную таблицу
ПерваяТЗ.Колонки.Добавить("ОстатокИюнь", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

// 2. Индексируем вторую таблицу для быстрого поиска
ВтораяТЗ.Индексы.Добавить("ФИО");

// 3. Обходим и сопоставляем
Для Каждого СтрокаТЗ1 Из ПерваяТЗ Цикл
    ПараметрыПоиска = Новый Структура("ФИО", СтрокаТЗ1.ФИО);
    СтрокаПоиска = ВтораяТЗ.Найти(СтрокаТЗ1.ФИО, "ФИО");
    
    Если СтрокаПоиска <> Неопределено Тогда
        СтрокаТЗ1.ОстатокИюнь = СтрокаПоиска.Остаток;
    КонецЕсли;
КонецЦикла;

Внимание: Этот метод («левое соединение» программным способом) не добавит в итоговую таблицу тех сотрудников, которые есть во ВторойТЗ, но отсутствуют в ПервойТЗ. Если вам нужно именно полное объединение, придется сначала собрать все уникальные ФИО в отдельный массив или таблицу, а потом заполнять данные по ним.

Типичные ошибки при объединении таблиц

Разберем основные грабли, на которые наступают разработчики:

1. Соединение по строковому представлению (ФИО)
Если в ваших таблицах в колонке ФИО хранится строка, а не ссылка на справочник Сотрудники, вы рискуете столкнуться с проблемой полных тезок. Кроме того, сравнение строк в базе данных работает медленнее, чем сравнение ссылок (GUID). Всегда старайтесь использовать ссылки в качестве ключей связи.

2. Дублирование записей
Если в одной из таблиц ключевое поле (например, Сотрудник) встречается дважды (например, разные подразделения), то при соединении (JOIN) количество строк в результате увеличится. Чтобы этого избежать, перед передачей в запрос используйте метод ТЗ.Свернуть("Сотрудник", "Остаток").

3. Проблема пустых значений (NULL)
Как мы уже выяснили, при использовании запроса важно не забывать про ЕСТЬNULL. Если в итоговой таблице вы увидите пустые ячейки там, где ожидался ноль, значит, вы пропустили обработку NULL значений, которые возвращает механизм соединений запроса 1С.

Выводы и рекомендации

Для большинства задач в 1С оптимальным является Способ №1 (Запрос с полным соединением). Он наиболее универсален, надежен и быстр. Если же вы работаете с отчетами, помните, что лучше всего не объединять таблицы программно, а использовать Схему компоновки данных (СКД). В СКД можно добавить два набора данных (ваши таблицы) и настроить связь между ними на закладке "Связи наборов данных". Система сама выполнит все соединения и корректно обработает отсутствие записей в одной из таблиц.

Рассмотренные нами методы позволяют гибко управлять данными и эффективно решать задачи по трансформации табличных структур в 1С.

← На главную