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