Объединение таблиц значений на встроенном языке: цикл, запрос, Свернуть()

· 10 мин

Содержание

Сложить две таблицы значений в 1С одной командой не получится: у объекта ТаблицаЗначений нет метода «Объединить» или «Добавить таблицу». Рабочий способ — скопировать первую таблицу через Скопировать() и в цикле дописать в неё строки второй с помощью ЗаполнитьЗначенияСвойств(). Если нужно объединить таблицы с суммированием по ключу, после этого вызывают Свернуть().

Типичная ситуация: обработка собирает остатки с двух складов в две таблицы, а печатной форме нужна одна. Или расширение получает данные из регистра и из внешнего файла и должно отдать их одним набором. Способ зависит от того, совпадают ли колонки и нужно ли суммировать строки по ключу.

Почему у таблицы значений нет метода «Объединить»

В сети часто советуют Таблица1.Объединить(Таблица2) или Таблица1.Загрузить(Таблица2). Такой код выдаст ошибку Метод объекта не обнаружен.

Что на самом деле есть у таблицы значений для этой задачи:

  • 📋 Скопировать(Строки, Колонки) — создаёт новую таблицу; можно взять часть строк и перечень колонок;
  • ➕ Добавить() — добавляет пустую строку и возвращает её;
  • 🔁 ЗаполнитьЗначенияСвойств(Приемник, Источник) — переносит значения одноимённых свойств, в том числе колонок строк таблиц;
  • 🧮 Свернуть(КолонкиГруппировок, КолонкиСуммирования) — группирует и суммирует;
  • 📥 ВыгрузитьКолонку() и ЗагрузитьКолонку() — работают с одной колонкой, а не со строками.

Метод Загрузить() есть у табличной части документа или справочника, а не у таблицы значений. Причём он не добавляет строки, а заменяет всё содержимое табличной части. Отсюда и путаница: в коде документа пишут Товары.Загрузить(ТЗ) и ждут, что строки допишутся.

Если нужно дописать таблицу значений в табличную часть, выгрузите табличную часть через Товары.Выгрузить(), добавьте строки в полученную таблицу, а затем загрузите её обратно Товары.Загрузить(Результат).

Где выполнять код

Тип ТаблицаЗначений недоступен в тонком клиенте и веб-клиенте. Код объединения пишите в процедурах с директивой &НаСервере, в модуле объекта или в общем модуле с флагом «Сервер».

Если исходные данные лежат в реквизите формы, сначала получите таблицу значений через РеквизитФормыВЗначение("Товары"), а после обработки верните её обратно через ЗначениеВРеквизитФормы().

Как сложить таблицы значений с одинаковыми колонками

Самый частый случай: обе таблицы получены одним запросом или созданы одним кодом, набор колонок совпадает. Здесь хватает пяти строк.

Результат = Таблица1.Скопировать();

Для Каждого СтрокаИсточника Из Таблица2 Цикл
    НоваяСтрока = Результат.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
КонецЦикла;

Что происходит по шагам:

  1. Скопировать() без параметров создаёт полную копию первой таблицы — со всеми строками, колонками и их типами. Исходная Таблица1 не меняется.
  2. Цикл проходит по каждой строке второй таблицы.
  3. Добавить() создаёт пустую строку в результате.
  4. ЗаполнитьЗначенияСвойств() переносит значения колонок с совпадающими именами.

Если первую таблицу сохранять не нужно, копирование можно пропустить и дописывать строки прямо в Таблица1. Это экономит память на больших объёмах.

Перенос только части колонок

У ЗаполнитьЗначенияСвойств() есть третий и четвёртый параметры: список заполняемых свойств и список исключаемых. Имена перечисляют через запятую в строке.

// перенести только номенклатуру и количество
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника, "Номенклатура, Количество");

// перенести всё, кроме служебной колонки
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника, , "НомерСтроки");

Второй вариант удобен, когда в обеих таблицах есть колонка с порядковым номером и её не нужно тащить в результат.

Объединение таблиц значений с разной структурой

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

Универсальная функция для общего модуля:

Функция ОбъединитьТаблицыЗначений(Таблица1, Таблица2) Экспорт

    Результат = Таблица1.Скопировать();

    // добавляем колонки, которых нет в первой таблице
    Для Каждого Колонка Из Таблица2.Колонки Цикл
        Если Результат.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
            Результат.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения, Колонка.Заголовок);
        КонецЕсли;
    КонецЦикла;

    // дописываем строки второй таблицы
    Для Каждого СтрокаИсточника Из Таблица2 Цикл
        ЗаполнитьЗначенияСвойств(Результат.Добавить(), СтрокаИсточника);
    КонецЦикла;

    Возврат Результат;

КонецФункции

В итоговой таблице будут все колонки обеих таблиц. У строк из первой таблицы новые колонки получат значения по умолчанию для своего типа: 0 для числа, пустую строку для строки, пустую ссылку для ссылочного типа.

Когда одноимённые колонки имеют разные типы

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

Пример: в Таблица1 колонка Код имеет тип «Число», в Таблица2 — «Строка» со значением "А-15". После объединения в результате окажется 0, и ошибки при этом не будет.

Перед объединением сравните типы колонок с одинаковыми именами, если таблицы приходят из разных источников. Проверка в коде:

Для Каждого Колонка Из Таблица2.Колонки Цикл
    КолонкаПриемника = Таблица1.Колонки.Найти(Колонка.Имя);
    Если КолонкаПриемника <> Неопределено
        И КолонкаПриемника.ТипЗначения <> Колонка.ТипЗначения Тогда
        Сообщить(СтрШаблон("Типы колонки %1 различаются", Колонка.Имя));
    КонецЕсли;
КонецЦикла;

Простой выход — при создании колонки указать составной тип через Новый ОписаниеТипов("Число, Строка") или оставить тип пустым. Колонка без описания типов принимает значения любого типа.

Готовая процедура в БСП

В типовых конфигурациях на Библиотеке стандартных подсистем (БП 3.0, ЗУП 3.1, УТ 11) есть процедура ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаИсточник, ТаблицаПриемник). Она дописывает строки источника в приёмник по совпадающим колонкам — по сути тот же цикл с ЗаполнитьЗначенияСвойств().

Перед использованием найдите её глобальным поиском в Конфигураторе (Правка → Глобальный поиск) и прочитайте комментарий к параметрам. В своей конфигурации без БСП её нет. Новые колонки она не добавляет — для разной структуры пригодится функция выше.

Сложение таблиц значений запросом: ОБЪЕДИНИТЬ ВСЕ

Запрос удобен, когда после объединения нужно сразу сгруппировать данные, убрать дубликаты, отфильтровать строки или соединить результат с данными базы.

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

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Первая
|ИЗ
|    &Таблица1 КАК Т
|;
|
|ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Вторая
|ИЗ
|    &Таблица2 КАК Т
|;
|
|ВЫБРАТЬ
|    ВТ_Первая.Номенклатура КАК Номенклатура,
|    ВТ_Первая.Количество КАК Количество
|ИЗ
|    ВТ_Первая КАК ВТ_Первая
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
|    ВТ_Вторая.Номенклатура,
|    ВТ_Вторая.Количество
|ИЗ
|    ВТ_Вторая КАК ВТ_Вторая";

Запрос.УстановитьПараметр("Таблица1", Таблица1);
Запрос.УстановитьПараметр("Таблица2", Таблица2);

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

Пакетный запрос видит свои временные таблицы и без явного менеджера временных таблиц. Менеджер нужен, если временные таблицы должны жить между разными запросами.

ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ

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

ОБЪЕДИНИТЬ сравнивает строки целиком, по всем полям выборки. Две строки с одной номенклатурой, но разным количеством, останутся обе.

Колонкам нужны типы

Для передачи в запрос у колонок таблицы значений должен быть задан тип. Если таблица создана вручную через Колонки.Добавить("Номенклатура") без описания типов, запрос упадёт с ошибкой о недопустимом типе в колонке.

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));

Таблицы, полученные через Выгрузить() из результата запроса, уже типизированы — с ними проблем нет.

Объединение с суммированием по ключу

Часто складывать нужно не строки, а значения: товар «Бумага А4» есть в обеих таблицах, количество 10 и 5, а в результате должна быть одна строка с количеством 15.

Способ через Свернуть()

Самый короткий вариант — объединить таблицы любым способом выше, затем свернуть:

Результат = ОбъединитьТаблицыЗначений(Таблица1, Таблица2);
Результат.Свернуть("Номенклатура, Склад", "Количество, Сумма");

Первый параметр — колонки группировки, второй — колонки, которые суммируются. Все прочие колонки Свернуть() удаляет из таблицы. Если колонка «Цена» нужна в результате, её придётся либо добавить в группировки, либо пересчитать после свёртки как Сумма / Количество.

Суммировать можно только числовые колонки. Строку или дату в список суммирования не ставьте.

Способ через запрос с группировкой

Если таблицы уже помещены во временные (см. раздел о запросе), последний запрос пакета меняется так:

|ВЫБРАТЬ
|    Об.Номенклатура КАК Номенклатура,
|    СУММА(Об.Количество) КАК Количество
|ИЗ
|    (ВЫБРАТЬ ВТ_Первая.Номенклатура КАК Номенклатура, ВТ_Первая.Количество КАК Количество
|     ИЗ ВТ_Первая КАК ВТ_Первая
|     ОБЪЕДИНИТЬ ВСЕ
|     ВЫБРАТЬ ВТ_Вторая.Номенклатура, ВТ_Вторая.Количество
|     ИЗ ВТ_Вторая КАК ВТ_Вторая) КАК Об
|
|СГРУППИРОВАТЬ ПО
|    Об.Номенклатура

Плюс запроса: в ту же выборку можно сразу добавить соединение со справочником и взять, например, единицу измерения или артикул.

Способ через поиск по индексу

Цикл с поиском нужен, когда к сложению добавляется своя логика: брать максимум вместо суммы, складывать только при совпадении даты, писать в лог расхождения.

Результат = Таблица1.Скопировать(, "Номенклатура, Количество");
Результат.Индексы.Добавить("Номенклатура");

Отбор = Новый Структура("Номенклатура");

Для Каждого СтрокаИсточника Из Таблица2 Цикл
    Отбор.Номенклатура = СтрокаИсточника.Номенклатура;
    Найденные = Результат.НайтиСтроки(Отбор);

    Если Найденные.Количество() = 0 Тогда
        СтрокаПриемника = Результат.Добавить();
        СтрокаПриемника.Номенклатура = СтрокаИсточника.Номенклатура;
    Иначе
        СтрокаПриемника = Найденные[0];
    КонецЕсли;

    СтрокаПриемника.Количество = СтрокаПриемника.Количество + СтрокаИсточника.Количество;
КонецЦикла;

Код рассчитан на то, что в первой таблице номенклатура не повторяется. Если повторы есть, сначала сверните её: Результат.Свернуть("Номенклатура", "Количество").

Колонка «Количество» должна иметь числовой тип. У колонки без типа новая строка получает значение Неопределено, и сложение с числом даст ошибку.

Какой способ выбрать

СпособКогда подходитОграничения
Цикл + ЗаполнитьЗначенияСвойств()одинаковые колонки, простое сложениедубликаты не убирает
Функция с выравниванием колонокразный набор колонокследите за типами одноимённых колонок
Запрос ОБЪЕДИНИТЬ ВСЕнужна группировка, фильтр, данные из базыколонки обязаны быть типизированы
Свернуть() после объединениясуммирование по ключуудаляет все колонки вне параметров
Поиск по индексу в циклесвоя логика слияния строкбольше кода
Что вам нужно получить после объединения?

Объединение больших таблиц значений: быстродействие

На таблицах в несколько сотен строк разница между способами незаметна. Она проявляется на десятках и сотнях тысяч строк.

Что реально влияет на скорость:

  • 🐢 Поиск без индекса в цикле. НайтиСтроки() по неиндексированной колонке перебирает всю таблицу. При 50 000 строк в каждой таблице это до 2,5 млрд сравнений. Одна строка Индексы.Добавить() перед циклом решает проблему.
  • 📦 Лишние копии. Каждый Скопировать() удваивает память. Если исходная таблица больше не нужна, дописывайте строки прямо в неё.
  • 🧾 Лишние колонки. Берите в Скопировать() только нужные колонки вторым параметром. Меньше колонок — меньше памяти и быстрее Свернуть().
  • 🔄 Запрос ради простого сложения. Помещение таблиц во временные — это передача данных на СУБД и обратно. Для сложения без группировки цикл обычно быстрее.
  • 🧮 Свернуть() вместо ручного суммирования. Встроенный метод выполняется платформой, а не интерпретатором встроенного языка, поэтому на чистом суммировании обходит цикл.

Индекс добавляйте после того, как первая таблица заполнена, а не до. При каждом добавлении строки платформа обновляет индекс, это тоже стоит времени.

Замер делайте не на глаз, а включив замер производительности в отладчике Конфигуратора. Он покажет, какая строка кода занимает больше всего времени.

Ошибки при объединении таблиц значений

Диагностика: выберите симптом

Частые вопросы

Можно ли объединить таблицы значений без цикла?
Без цикла — только запросом через временные таблицы и ОБЪЕДИНИТЬ ВСЕ. Для простого сложения цикл короче и обычно быстрее.
Сохранится ли порядок строк после объединения?
При цикле — да: сначала строки первой таблицы, затем второй. Результат запроса без УПОРЯДОЧИТЬ ПО порядок не гарантирует. После объединения можно вызвать Сортировать().
Как объединить больше двух таблиц?
Вызовите функцию объединения в цикле по массиву таблиц, накапливая результат. В запросе добавьте нужное число временных таблиц и блоков ОБЪЕДИНИТЬ ВСЕ.
Как объединить таблицы значений «по горизонтали», добавив колонки по ключу?
Это соединение, а не объединение. Делайте его запросом с ЛЕВОЕ СОЕДИНЕНИЕ по ключевому полю либо циклом с НайтиСтроки() и заполнением новых колонок.
Как пометить, из какой таблицы пришла строка?
Добавьте в результат колонку Источник типа Строка и в каждом цикле заполняйте её своим значением, например «Склад 1» и «Склад 2». В запросе то же делается полем-константой в каждом блоке ОБЪЕДИНИТЬ ВСЕ.