Как объединить две таблицы значений с помощью запроса при неизвестной структуре колонок

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработчика 1С часто возникает задача объединения данных из двух объектов ТаблицаЗначений. Если структура таблиц заранее известна, написание запроса не составляет труда. Однако, если состав колонок может меняться или вовсе неизвестен на этапе разработки, стандартный подход требует доработки. Рассмотрим подробнее, как эффективно решить эту задачу, проанализируем возможные ошибки и выберем оптимальный метод в зависимости от контекста.

Почему не работает ВЫБРАТЬ * в объединениях

Разберем типичную ситуацию: у нас есть две таблицы ТЗ_1 и ТЗ_2 с одинаковым набором колонок. Попытка использовать конструкцию ВЫБРАТЬ * внутри оператора ОБЪЕДИНИТЬ в языке запросов 1С часто приводит к ошибке. Это происходит потому, что платформа требует жесткого соответствия количества, порядка и типов полей во всех частях объединения. При использовании "звездочки" порядок полей в памяти может не совпадать с порядком, ожидаемым менеджером временных таблиц, или метаданные колонок могут быть недостаточно типизированы.

Способ №1: Динамическое формирование текста запроса

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

Рассмотрим пошагово, как реализовать такую функцию:

  1. Перебираем колонки первой таблицы для формирования строки с именами полей.
  2. Вставляем полученную строку в шаблон текста запроса.
  3. Устанавливаем таблицы как параметры и выполняем запрос.

Функция СклеитьТЗ_Динамически(ТЗ_1, ТЗ_2)
    
    СписокПолей = "";
    Для Каждого Колонка Из ТЗ_1.Колонки Цикл
        СписокПолей = СписокПолей + "ТЗ." + Колонка.Имя + " КАК " + Колонка.Имя + ", ";
    КонецЦикла;
    
    // Удаляем лишнюю запятую в конце
    СписокПолей = Лев(СписокПолей, СтрДлина(СписокПолей) - 2);
    
    ТекстЗапроса = 
    "ВЫБРАТЬ
    |    " + СписокПолей + "
    |ПОМЕСТИТЬ ВТ_1
    |ИЗ
    |    &ТЗ_1 КАК ТЗ
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    " + СписокПолей + "
    |ПОМЕСТИТЬ ВТ_2
    |ИЗ
    |    &ТЗ_2 КАК ТЗ
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ * ИЗ ВТ_1
    |ОБЪЕДИНИТЬ ВСЕ
    |ВЫБРАТЬ * ИЗ ВТ_2";
    
    Запрос = Новый Запрос(ТекстЗапроса);
    Запрос.УстановитьПараметр("ТЗ_1", ТЗ_1);
    Запрос.УстановитьПараметр("ТЗ_2", ТЗ_2);
    
    Возврат Запрос.Выполнить().Выгрузить();
КонецФункции

Способ №2: Использование временных таблиц через МенеджерВременныхТаблиц

Выясним причину, по которой иногда удается использовать ВЫБРАТЬ *. Если мы помещаем каждую таблицу в отдельную временную таблицу через метод Выполнить(), платформа фиксирует структуру. После этого мы можем объединить их в третьем запросе, используя тот же менеджер. Однако помните: типизация колонок обязательна. Если колонка в ТаблицаЗначений не имеет четко заданного типа (объект ОписаниеТипов), запрос завершится с ошибкой.

Посмотрим на пример реализации этого подхода:


МенеджерВТ = Новый МенеджерВременныхТаблиц;

// Помещаем первую таблицу
Запрос1 = Новый Запрос("ВЫБРАТЬ * ПОМЕСТИТЬ ВТ1 ИЗ &ТЗ1 КАК ТЗ");
Запрос1.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос1.УстановитьПараметр("ТЗ1", ТЗ1);
Запрос1.Выполнить();

// Помещаем вторую таблицу
Запрос2 = Новый Запрос("ВЫБРАТЬ * ПОМЕСТИТЬ ВТ2 ИЗ &ТЗ2 КАК ТЗ");
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.УстановитьПараметр("ТЗ2", ТЗ2);
Запрос2.Выполнить();

// Объединяем результат
ЗапросИтог = Новый Запрос("ВЫБРАТЬ * ИЗ ВТ1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ * ИЗ ВТ2");
ЗапросИтог.МенеджерВременныхТаблиц = МенеджерВТ;
ТЗИтого = ЗапросИтог.Выполнить().Выгрузить();

Способ №3: Объединение через цикл (классический подход)

Не всегда запрос является лучшим решением. Проанализируем ситуацию с точки зрения производительности. Передача таблицы значений в запрос подразумевает сериализацию данных и их отправку на SQL-сервер. Если нам нужно просто "склеить" две таблицы в памяти на стороне сервера приложений, обычный цикл может оказаться быстрее.

Оптимальный вариант с использованием ЗаполнитьЗначенияСвойств:


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

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

Когда стоит использовать запрос?

Рассмотрим ситуации, когда использование запроса для объединения ТЗ оправдано:

  1. Дальнейшая обработка: Если после объединения вам нужно выполнить группировку (СГРУППИРОВАТЬ ПО), сортировку или соединение этих данных с физическими таблицами базы данных.
  2. Удаление дублей: Использование оператора ОБЪЕДИНИТЬ (без ВСЕ) автоматически удалит полностью идентичные строки, что удобно для получения уникальных записей.
  3. Типизация "на лету": В запросе можно явно привести типы полей с помощью функции ВЫРАЗИТЬ, если в исходных таблицах типы колонок различаются.

Важность типизации колонок

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

Правильный способ создания колонки:

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

Альтернативы из БСП

Если в вашей конфигурации используется "Библиотека стандартных подсистем" (БСП), рекомендуем обратить внимание на готовые методы. Например, процедура ОбщегоНазначения.ДополнитьТаблицу позволяет объединить данные двух таблиц максимально эффективно. Разработчики БСП уже предусмотрели оптимизацию этих процессов, поэтому использование стандартных функций повышает надежность кода.

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

← На главную