В процессе разработки на платформе 1С:Предприятие часто возникает задача объединения нескольких таблиц значений в одну общую структуру. Это может потребоваться, когда данные уже получены из разных источников, загружены из файлов, раздельных запросов или обработаны в различных модулях системы. В данной статье мы подробно разберем все возможные способы решения этой задачи: от простых циклов до использования встроенных функций Библиотеки Стандартных Подсистем (БСП) и высокопроизводительных низкоуровневых методов работы с коллекциями.
Это классический и наиболее очевидный способ, который часто используют начинающие разработчики. Он заключается в том, что мы создаем результирующую таблицу, а затем в цикле обходим строки каждой исходной таблицы, добавляя новые строки в приемник и копируя туда данные с помощью встроенной функции ЗаполнитьЗначенияСвойств.
Давайте рассмотрим, как выглядит такой алгоритм на практике. Предположим, у нас есть три таблицы: ТаблицаДолгов1, ТаблицаДолгов2 и ТаблицаДолгов3. Все они имеют одинаковую структуру колонок.
// Создаем результирующую таблицу на основе структуры первой таблицы
ТаблицаДолгов = ТаблицаДолгов1.СкопироватьКолонки();
// Помещаем исходные таблицы в массив для удобства обхода
МассивТаблиц = Новый Массив;
МассивТаблиц.Добавить(ТаблицаДолгов1);
МассивТаблиц.Добавить(ТаблицаДолгов2);
МассивТаблиц.Добавить(ТаблицаДолгов3);
// Объединяем данные в цикле
Для Каждого ТекущаяТаблица Из МассивТаблиц Цикл
Для Каждого СтрокаИсточника Из ТекущаяТаблица Цикл
ЗаполнитьЗначенияСвойств(ТаблицаДолгов.Добавить(), СтрокаИсточника);
КонецЦикла;
КонецЦикла;
Преимущества метода: простота понимания и универсальность. Этот код гарантированно будет работать на любой конфигурации и любой версии платформы 1С.
Недостатки: низкая производительность при работе с большими объемами данных (десятки и сотни тысяч строк), так как обход и добавление каждой строки на встроенном языке выполняются относительно медленно.
Если вы работаете в любой типовой или современной кастомной конфигурации 1С, разработанной на базе БСП, вам не нужно писать циклы вручную. В библиотеке уже реализован готовый оптимизированный инструмент для слияния таблиц. Нам достаточно вызвать метод ДополнитьТаблицу из общего модуля ОбщегоНазначенияКлиентСервер.
Разберем этот лаконичный и профессиональный подход по шагам:
// Создаем пустую результирующую таблицу с аналогичной структурой колонок
ТаблицаДолгов = ТаблицаДолгов1.Скопировать(Новый Массив);
// Используем стандартный метод БСП для дополнения таблицы данными
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов1, ТаблицаДолгов);
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов2, ТаблицаДолгов);
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов3, ТаблицаДолгов);
Обратите внимание на конструкцию ТаблицаДолгов1.Скопировать(Новый Массив). Этот метод позволяет мгновенно получить пустую копию таблицы значений с сохранением всех ее колонок и типов данных без копирования самих строк.
Важный совет по архитектуре: чтобы обезопасить свой код от возможных изменений в БСП при обновлении конфигурации, некоторые разработчики используют модуль-посредник (например, свой собственный общий модуль проекта), внутри которого вызывается метод БСП. Если в будущем разработчики 1С переименуют или переместят метод БСП, вам придется исправить код только в одном месте вашего модуля-посредника.
Давайте проанализируем ошибку, которую часто допускают новички на этапе обучения. Попытка объединить данные иногда выглядит следующим образом:
ТаблицаДолгов = Новый Массив;
Для Каждого Таб Из ТаблицаДолгов1 Цикл
ТаблицаДолгов.Добавить(Таб);
КонецЦикла;
Выясним, почему этот код в корне неверный и приведет к ошибкам в логике программы:
ТаблицаДолгов становится объектом типа Массив, а не ТаблицаЗначений. Вы полностью теряете возможность использовать такие мощные встроенные методы таблиц, как НайтиСтроки, Скопировать, Свернуть, Сортировать и другие.Добавить() у массива в данном случае сохраняет ссылки на строки исходной таблицы значений ТаблицаДолгов1. Если исходная таблица будет очищена, удалена из памяти или изменена в другом месте кода, данные в вашем массиве также изменятся или станут невалидными. Всегда создавайте физически новые строки в новой таблице значений.Что делать, если таблицы содержат сотни тысяч строк, а БСП использовать не хочется или ее нет в конфигурации? Существует высокопроизводительный обходной алгоритм, который работает в разы быстрее обычного построчного цикла. Он основан на использовании быстрых низкоуровневых методов платформы Вставить и ЗагрузитьКолонку.
Давайте посмотрим на пример реализации для двух таблиц:
// Делаем копию второй таблицы
Результат = ТаблицаДолгов2.Скопировать();
// Быстро резервируем пустые строки в начале таблицы под размер первой таблицы
КоличествоСтрокТ1 = ТаблицаДолгов1.Количество();
Для Индекс = 1 По КоличествоСтрокТ1 Цикл
Результат.Вставить(0);
КонецЦикла;
// Переносим данные первой таблицы поколонно
Для ИндексКолонки = 0 По ТаблицаДолгов1.Колонки.Количество() - 1 Цикл
ИмяКолонки = ТаблицаДолгов1.Колонки[ИндексКолонки].Имя;
МассивДанныхКолонки = ТаблицаДолгов1.ВыгрузитьКолонку(ИмяКолонки);
Результат.ЗагрузитьКолонку(МассивДанныхКолонки, ИмяКолонки);
КонецЦикла;
Выясним причину высокой скорости работы: Метод Вставить(0) создает пустую строку в самом начале таблицы. Мы создаем ровно столько строк, сколько записей в первой таблице. Затем мы выгружаем данные первой таблицы по колонкам в массивы и загружаем их в результирующую таблицу с помощью ЗагрузитьКолонку. Поскольку переданный массив по длине соответствует количеству добавленных пустых строк, платформа быстро заполняет их данными, не затрагивая строки второй таблицы, находящиеся ниже. Этот метод невероятно производителен, так как основные операции выполняются на уровне платформенного C++ кода без интерпретации циклов встроенного языка 1С.
Если таблицы значений уже сформированы в коде, мы можем передать их в качестве параметров в новый запрос. Запрос выполнит объединение на уровне временных таблиц платформы. Это особенно удобно, если данные нужно не просто слить, а еще отфильтровать, сгруппировать или отсортировать. Для решения подобных задач в запросах на новых версиях платформы подойдёт разбор механизма добавления данных в существующие временные таблицы.
Рассмотрим пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Таблица1 ИЗ &Таблица1 КАК Таблица1;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Таблица2 ИЗ &Таблица2 КАК Таблица2;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Таблица3 ИЗ &Таблица3 КАК Таблица3;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ * ИЗ ВТ_Таблица1 КАК ВТ1
|ОБЪЕДИНИТЬ ВСЕ
|ВЫБРАТЬ * ИЗ ВТ_Таблица2 КАК ВТ2
|ОБЪЕДИНИТЬ ВСЕ
|ВЫБРАТЬ * ИЗ ВТ_Таблица3 КАК ВТ3";
Запрос.УстановитьПараметр("Таблица1", ТаблицаДолгов1);
Запрос.УстановитьПараметр("Таблица2", ТаблицаДолгов2);
Запрос.УстановитьПараметр("Таблица3", ТаблицаДолгов3);
ТаблицаДолгов = Запрос.Выполнить().Выгрузить();
Важное требование безопасности: Чтобы передать таблицу значений в запрос в качестве параметра, у всех ее колонок должны быть строго определены типы данных (объект ОписаниеТипов). Если типы колонок будут неопределенными (со значением Неопределено), платформа выдаст ошибку во время выполнения запроса.
Если исходные таблицы имеют разную структуру (например, в одной таблице есть колонка Сделка, а в другой — только Контрагент и Сумма), то при простом использовании метода ЗаполнитьЗначенияСвойств данные по уникальным колонкам будут потеряны. Нам необходимо предварительно расширить структуру колонок результирующей таблицы.
Разберем, как реализовать этот алгоритм динамического объединения:
// Создаем результирующую таблицу как копию первой
ТаблицаДолгов = ТаблицаДолгов1.Скопировать();
// Создаем массив остальных таблиц для слияния
ОстальныеТаблицы = Новый Массив;
ОстальныеТаблицы.Добавить(ТаблицаДолгов2);
ОстальныеТаблицы.Добавить(ТаблицаДолгов3);
Для Каждого ТекущаяТаблица Из ОстальныеТаблицы Цикл
// Добавляем отсутствующие колонки в таблицу-приемник
Для Каждого Колонка Из ТекущаяТаблица.Колонки Цикл
Если ТаблицаДолгов.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
ТаблицаДолгов.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения, Колонка.Заголовок);
КонецЕсли;
КонецЦикла;
// Заполняем данные
Для Каждого СтрокаИсточника Из ТекущаяТаблица Цикл
ЗаполнитьЗначенияСвойств(ТаблицаДолгов.Добавить(), СтрокаИсточника);
КонецЦикла;
КонецЦикла;
Этот код гарантирует, что ни одна уникальная колонка из объединяемых таблиц значений не будет потеряна, а недостающие ячейки в строках заполнятся значениями по умолчанию.
Если вам требуется объединить таблицы так, чтобы повторяющиеся строки схлопывались в одну (аналог операции ОБЪЕДИНИТЬ без ключевого слова ВСЕ в запросах), воспользуемся методом Свернуть. Этот метод группирует строки по ключевым полям и суммирует числовые показатели.
Рассмотрим пример, где мы объединяем таблицы и удаляем дубликаты строк по ключевым полям:
// 1. Объединяем таблицы любым удобным способом (например, через БСП)
ТаблицаДолгов = ТаблицаДолгов1.Скопировать(Новый Массив);
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов1, ТаблицаДолгов);
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов2, ТаблицаДолгов);
ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаДолгов3, ТаблицаДолгов);
// 2. Формируем списки колонок для группировки и суммирования
МассивГруппировок = Новый Массив;
МассивСумм = Новый Массив;
Для Каждого Колонка Из ТаблицаДолгов.Колонки Цикл
// Если колонка числовая — это ресурс для суммирования
Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
МассивСумм.Добавить(Колонка.Имя);
Иначе
МассивГруппировок.Добавить(Колонка.Имя);
КонецЕсли;
КонецЦикла;
СтрокаГруппировок = СтрСоединить(МассивГруппировок, ",");
СтрокаСумм = СтрСоединить(МассивСумм, ",");
// 3. Сворачиваем таблицу
ТаблицаДолгов.Свернуть(СтрокаГруппировок, СтрокаСумм);
После выполнения данного алгоритма результирующая таблица будет содержать только уникальные комбинации ключевых полей, а все числовые данные (например, суммы задолженности) будут корректно просуммированы.