При работе в системе 1С:Предприятие мы часто сталкиваемся с необходимостью обработки данных, представленных в виде Таблиц Значений (ТЗ). Иногда возникает задача объединить две такие таблицы, но с одной оговоркой: состав колонок в них может быть разным. Как же эффективно решить эту задачу, чтобы получить одну цельную таблицу, содержащую данные из обеих исходных?
Давайте рассмотрим несколько подходов к объединению таблиц значений с разным составом колонок в 1С. Мы проанализируем как мощные инструментальные средства, такие как запросы с временными таблицами, так и программные методы, позволяющие достичь нужного результата.
Этот способ является одним из наиболее гибких и мощных. Он позволяет нам использовать всю мощь языка запросов 1С для манипуляции данными. Суть подхода заключается в том, что обе исходные Таблицы Значений мы сначала выгружаем во временные таблицы с помощью Менеджера временных таблиц. Затем мы пишем запрос, который соединяет или объединяет эти временные таблицы по нужным нам правилам. При необходимости для объединения данных на платформе 8.3.25 можно использовать механизм добавления данных в существующую временную таблицу.
Создание Менеджера временных таблиц.
Прежде всего, нам понадобится объект МенеджерВременныхТаблиц. Он будет служить контейнером для наших временных таблиц.
МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Выгрузка Таблиц Значений во временные таблицы.
Каждую исходную Таблицу Значений мы должны выгрузить во временную таблицу, присвоив ей уникальное имя. Например, "ТЗ1Исходная" и "ТЗ2Исходная".
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
// Выгрузка первой ТЗ
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ1Исходная ИЗ &ТЗ1 КАК ТЗ1Исходная";
Запрос.УстановитьПараметр("ТЗ1", ТЗ1); // Предполагается, что ТЗ1 уже существует
Запрос.Выполнить();
// Выгрузка второй ТЗ
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ2Исходная ИЗ &ТЗ2 КАК ТЗ2Исходная";
Запрос.УстановитьПараметр("ТЗ2", ТЗ2); // Предполагается, что ТЗ2 уже существует
Запрос.Выполнить();
Построение запроса для объединения или соединения.
Теперь, когда наши данные находятся во временных таблицах, мы можем построить основной запрос. В зависимости от того, как мы хотим объединить данные, мы можем использовать операторы СОЕДИНЕНИЕ (JOIN) или ОБЪЕДИНИТЬ (UNION).
Если нам необходимо объединить строки, у которых есть совпадающие значения по определенным ключевым колонкам (например, по уникальному идентификатору, коду или наименованию), мы будем использовать оператор СОЕДИНЕНИЕ. 1С поддерживает различные виды соединений: ЛЕВОЕ СОЕДИНЕНИЕ, ПРАВОЕ СОЕДИНЕНИЕ, ПОЛНОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Выбор типа соединения зависит от того, какие строки (из какой таблицы) мы хотим сохранить в результирующей выборке при отсутствии совпадений.
В результирующую выборку мы можем включить все необходимые колонки из обеих таблиц. Если какая-либо колонка может отсутствовать в одной из таблиц после соединения (например, при ЛЕВОМ или ПРАВОМ СОЕДИНЕНИИ), мы можем использовать функцию ЕСТЬNULL. Она позволит нам подставить значение из другой таблицы или значение по умолчанию (например, 0 или пустую строку), если исходное значение равно NULL.
Рассмотрим пример, где у нас есть две таблицы, и мы хотим соединить их по колонке "Код", а также сложить значения "Количество1" и "Количество2", если они присутствуют:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ1Исходная.Код КАК Код,
| ТЗ1Исходная.Наименование КАК Наименование1,
| ТЗ2Исходная.Наименование КАК Наименование2,
| ЕСТЬNULL(ТЗ1Исходная.Количество1, 0) + ЕСТЬNULL(ТЗ2Исходная.Количество2, 0) КАК ИтоговоеКоличество,
| ТЗ1Исходная.ДополнительноеПоле1,
| ТЗ2Исходная.ДополнительноеПоле2
|ИЗ
| ВТ.ТЗ1Исходная КАК ТЗ1Исходная
| ПОЛНОЕ СОЕДИНЕНИЕ ВТ.ТЗ2Исходная КАК ТЗ2Исходная
| ПО ТЗ1Исходная.Код = ТЗ2Исходная.Код";
РезультатЗапроса = Запрос.Выполнить();
ИтоговаяТЗ = РезультатЗапроса.Выгрузить();
Здесь мы использовали ПОЛНОЕ СОЕДИНЕНИЕ, чтобы получить все строки из обеих таблиц, сопоставляя их по полю "Код". Для колонок "Количество1" и "Количество2" мы использовали ЕСТЬNULL, чтобы гарантировать, что при отсутствии значения в одной из таблиц подставится 0, и суммирование произойдет корректно. Обратите внимание, что доступ к временным таблицам осуществляется через префикс ВТ.
Если наша задача – просто добавить строки одной таблицы к строкам другой, словно "вставить в конец", то мы будем использовать оператор ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ. Основное отличие между ними: ОБЪЕДИНИТЬ удаляет дубликаты строк, а ОБЪЕДИНИТЬ ВСЕ оставляет все строки, включая дубликаты.
Критически важный момент при использовании ОБЪЕДИНИТЬ для таблиц с разным составом колонок: необходимо привести их к одинаковой структуре в запросе. Это означает, что в каждой части запроса ВЫБРАТЬ мы должны явно указать все колонки, которые хотим видеть в итоговой таблице, в одном и том же порядке. Если колонка присутствует только в одной из ТЗ, то для другой ТЗ в запросе на ее место можно подставить NULL или любое другое подходящее значение, чтобы количество и порядок колонок в обеих частях ОБЪЕДИНИТЬ совпадали.
Давайте посмотрим на пример:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ1Исходная.Код КАК Код,
| ТЗ1Исходная.Наименование КАК Наименование,
| ТЗ1Исходная.Количество КАК Количество,
| NULL КАК Сумма, // Колонка Сумма отсутствует в ТЗ1, но есть в ТЗ2
| ТЗ1Исходная.ДатаСоздания КАК ДатаСоздания // Колонка ДатаСоздания есть в ТЗ1, но нет в ТЗ2
|ИЗ
| ВТ.ТЗ1Исходная КАК ТЗ1Исходная
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ТЗ2Исходная.Код,
| ТЗ2Исходная.Наименование,
| NULL, // Колонка Количество отсутствует в ТЗ2, но есть в ТЗ1
| ТЗ2Исходная.Сумма,
| NULL // Колонка ДатаСоздания отсутствует в ТЗ2
|ИЗ
| ВТ.ТЗ2Исходная КАК ТЗ2Исходная";
РезультатЗапроса = Запрос.Выполнить();
ИтоговаяТЗ = РезультатЗапроса.Выгрузить();
В этом примере мы явно указали все колонки для обеих частей объединения. Для отсутствующих колонок мы использовали NULL, чтобы сохранить соответствие структуры.
Стандартный подход к программному объединению часто включает создание копии одной из Таблиц Значений и построчное добавление в нее данных из второй ТЗ в цикле. Для копирования значений свойств (колонок) из строки-источника в новую строку таблицы-приемника обычно используется функция ЗаполнитьЗначенияСвойств(). Однако, если колонки присутствуют только в источнике, они не будут автоматически добавлены в таблицу-приемник без предварительного создания таких колонок в приемнике.
Давайте рассмотрим более оптимизированный подход, который может быть значительно быстрее построчного копирования, особенно когда число колонок значительно меньше числа строк. Этот "трюк" позволяет нам эффективно использовать метод ЗагрузитьКолонку().
Создание результирующей Таблицы Значений.
Мы создаем новую Таблицу Значений, которая будет служить нашим результатом. Обычно, для удобства, ее структура изначально копируется с одной из исходных ТЗ. Например, со второй ТЗ, которую мы планируем "добавить".
ИтоговаяТЗ = ТЗ2.Скопировать(); // Копируем структуру и данные из ТЗ2
Добавление колонок из первой ТЗ.
Теперь мы должны добавить в нашу ИтоговуюТЗ все уникальные колонки из ТЗ1, которых нет в ТЗ2.
Для Каждого КолонкаТЗ1 Из ТЗ1.Колонки Цикл
Если ИтоговаяТЗ.Колонки.Найти(КолонкаТЗ1.Имя) = Неопределено Тогда
ИтоговаяТЗ.Колонки.Добавить(КолонкаТЗ1.Имя, КолонкаТЗ1.ТипЗначения);
КонецЕсли;
КонецЦикла;
"Освобождение места" для данных из первой ТЗ.
В начало этой результирующей таблицы мы вставляем столько пустых строк, сколько строк содержится в первой ТЗ. Таким образом, мы "освобождаем место" для данных из первой таблицы, которые будем загружать.
Для Счетчик = 1 По ТЗ1.Количество() Цикл
ИтоговаяТЗ.Вставить(0, ИтоговаяТЗ.НоваяСтрока());
КонецЦикла;
Загрузка колонок из первой ТЗ.
Теперь мы можем поочередно загрузить колонки из первой ТЗ в соответствующие колонки результирующей таблицы с помощью метода ЗагрузитьКолонку(). Важным моментом является то, что метод ЗагрузитьКолонку() по умолчанию заменяет существующие значения, начиная с первой строки. Поскольку мы вставили пустые строки в начало, данные из ТЗ1 аккуратно заполнят эти новые, пустые строки.
Для Каждого КолонкаТЗ1 Из ТЗ1.Колонки Цикл
Если ИтоговаяТЗ.Колонки.Найти(КолонкаТЗ1.Имя) <> Неопределено Тогда
МассивЗначенийКолонки = ТЗ1.ВыгрузитьКолонку(КолонкаТЗ1.Имя);
ИтоговаяТЗ.ЗагрузитьКолонку(МассивЗначенийКолонки, КолонкаТЗ1.Имя);
КонецЕсли;
КонецЦикла;
Этот метод оказывается особенно эффективным, когда мы хотим объединить данные, просто дописывая одну таблицу к другой, при этом обеспечивая, что все уникальные колонки будут присутствовать в итоговой ТЗ. Метод ЗагрузитьКолонку() значительно быстрее построчного копирования данных через циклы.
В некоторых сложных сценариях нам требуется не просто сложить таблицы или выполнить простое соединение, а реализовать полноценное ПОЛНОЕ СОЕДИНЕНИЕ по аналогии с запросами, когда есть ключевые поля для сопоставления, и мы хотим объединить информацию из обеих таблиц в одну строку по совпадению этих ключей.
Для такой задачи мы можем написать собственную универсальную функцию, которая будет выполнять следующие шаги:
Определение структуры результирующей ТЗ.
Мы создаем новую, пустую Таблицу Значений. Затем мы итерируем по колонкам первой и второй исходных ТЗ, добавляя все уникальные колонки в нашу результирующую ТЗ. Это гарантирует, что в итоговой таблице будут присутствовать все возможные поля из обеих исходных таблиц.
Функция ОбъединитьТЗПоКлючам(ТЗ1, ТЗ2, КлючевыеПоля) Экспорт
ИтоговаяТЗ = Новый ТаблицаЗначений;
// Добавляем все колонки из ТЗ1
Для Каждого Колонка Из ТЗ1.Колонки Цикл
ИтоговаяТЗ.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЦикла;
// Добавляем уникальные колонки из ТЗ2
Для Каждого Колонка Из ТЗ2.Колонки Цикл
Если ИтоговаяТЗ.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
ИтоговаяТЗ.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЕсли;
КонецЦикла;
// ... продолжение функции
Кодбиг></li>
<li><p><strong>Итерация по первой ТЗ и попытка сопоставления со второй.</strong></p>
<p>Мы итерируем по каждой строке первой ТЗ. Для каждой строки:</p>
<ol type="a">
<li>Добавляем ее в результирующую ТЗ как новую строку.</li>
<li>Затем мы пытаемся найти соответствующую строку во второй ТЗ по заданным <code>КлючевымПолям</code>.</li>
<li>Если соответствие найдено, мы заполняем недостающие колонки (которые есть только во второй ТЗ) значениями из найденной строки второй ТЗ.</li>
<li>Чтобы избежать повторной обработки, мы можем пометить найденные строки во второй ТЗ (например, добавив временную булеву колонку в ТЗ2 и устанавливая ее в <code>Истина</code>).</li>
</ol>
<codebig>
// ... продолжение функции
НайденныеСтрокиТЗ2 = Новый Соответствие; // Для отслеживания уже сопоставленных строк ТЗ2
Для Каждого СтрокаТЗ1 Из ТЗ1 Цикл
НоваяСтрока = ИтоговаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ1); // Копируем все из ТЗ1
// Ищем соответствующую строку в ТЗ2
СоответствующаяСтрокаТЗ2 = Неопределено;
Для Каждого СтрокаТЗ2 Из ТЗ2 Цикл
Если НайденныеСтрокиТЗ2.Получить(СтрокаТЗ2) <> Истина Тогда // Если строка ТЗ2 еще не была сопоставлена
СовпадениеКлючей = Истина;
Для Каждого КлючПоле Из КлючевыеПоля Цикл
Если СтрокаТЗ1[КлючПоле] <> СтрокаТЗ2[КлючПоле] Тогда
СовпадениеКлючей = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
Если СовпадениеКлючей Тогда
СоответствующаяСтрокаТЗ2 = СтрокаТЗ2;
НайденныеСтрокиТЗ2.Вставить(СтрокаТЗ2, Истина); // Помечаем как найденную
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Если найдена соответствующая строка в ТЗ2, заполняем недостающие поля
Если СоответствующаяСтрокаТЗ2 <> Неопределено Тогда
Для Каждого Колонка Из СоответствующаяСтрокаТЗ2.ПолучитьСтруктуруСтроки() Цикл
Если ИтоговаяТЗ.Колонки.Найти(Колонка.Ключ) <> Неопределено
И НоваяСтрока.Свойство(Колонка.Ключ) = Ложь Тогда // Проверяем, что поля из ТЗ1 уже не перезаписаны
НоваяСтрока[Колонка.Ключ] = Колонка.Значение;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
// ... продолжение функции
Добавление несопоставленных строк из второй ТЗ.
После обработки первой ТЗ, мы итерируем по строкам второй ТЗ. Если строка из второй ТЗ еще не была сопоставлена (мы можем это проверить по нашей временной пометке), значит, она не нашла пары в первой ТЗ. Мы добавляем эти строки как новые в результирующую ТЗ, заполняя существующие поля и оставляя NULL (или значения по умолчанию) для тех колонок, которых нет в ТЗ2, но есть в ТЗ1.
// ... продолжение функции
// Добавляем строки из ТЗ2, которые не были сопоставлены
Для Каждого СтрокаТЗ2 Из ТЗ2 Цикл
Если НайденныеСтрокиТЗ2.Получить(СтрокаТЗ2) = Неопределено Тогда // Если строка ТЗ2 не была сопоставлена
НоваяСтрока = ИтоговаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ2); // Копируем все из ТЗ2
КонецЕсли;
КонецЦикла;
Возврат ИтоговаяТЗ;
КонецФункции
Этот метод, хотя и требует более сложной реализации, обеспечивает полноценное соединение таблиц по заданным критериям, что особенно полезно при формировании сложных отчетов, где данные поступают из разных выборок, и необходимо получить полную картину со всеми данными из обеих источников. Он позволяет создать универсальную функцию, которую можно переиспользовать в различных частях вашего решения, просто передавая нужные ключевые поля.
Как мы видим, в 1С есть разнообразные инструменты для объединения Таблиц Значений с разным составом колонок. Выбор конкретного подхода зависит от поставленной задачи: требуется ли нам просто дописать одну таблицу к другой, выполнить соединение по ключам или собрать полный набор данных из двух источников. Каждый из рассмотренных методов имеет свои преимущества и может быть эффективно применен в соответствующей ситуации.