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

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

При написании сложных запросов с передачей внешней таблицы значений (параметр &ТЗ) разработчики часто сталкиваются с синтаксической ошибкой при попытке применить конструкцию ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ непосредственно к источнику-параметру.

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

В чем причина ошибки в запросе?

Посмотрим на исходный запрос, в котором возникает ошибка:


Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЗ.ОргИНН КАК ИНН,
    |    ТЗ.ОргКПП КАК КПП        
    |ПОМЕСТИТЬ ПоИНН
    |ИЗ
    |    &ТЗ КАК ТЗ
    |ГДЕ
    |    (ТЗ.КонВыгр = 2) Или (ТЗ.КонВыгр = 4)
    |
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    |    ТЗ.КонтрИНН КАК ИНН,
    |    ТЗ.КонтрКПП КАК КПП        
    |ИЗ
    |    &ТЗ КАК ТЗ
    |;
    |ВЫБРАТЬ ...";

Выясним причину такого поведения платформы. В языке запросов 1С при работе с внешними наборами данных, переданными через параметры (объект ТаблицаЗначений, табличные части), действуют строгие ограничения. К таблице-параметру нельзя напрямую применять следующие операции:

  1. Операторы объединения ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ.
  2. Любые виды соединений (СОЕДИНЕНИЕ).
  3. Группировку данных с помощью предложений СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ.
  4. Построение итогов с использованием ИТОГИ.
  5. Автоматическое упорядочивание АВТОУПОРЯДОЧИВАНИЕ.
  6. Разыменование ссылочных полей через точку.

Единственное доступное действие с внешним источником данных — напрямую выгрузить его строки во временную таблицу базы данных с помощью конструкции ПОМЕСТИТЬ. Только после того, как данные физически окажутся во временной таблице СУБД, с ними можно выполнять любые стандартные реляционные операции: соединять, группировать и объединять.

Разберем подробнее два надежных способа решения поставленной задачи.

Способ 1. Загрузка исходной таблицы во временную за один проход (рекомендуемый)

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

Разберем реализацию по шагам:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЗ.ОргИНН КАК ОргИНН,
    |    ТЗ.ОргКПП КАК ОргКПП,
    |    ТЗ.КонтрИНН КАК КонтрИНН,
    |    ТЗ.КонтрКПП КАК КонтрКПП,
    |    ТЗ.КонВыгр КАК КонВыгр
    |ПОМЕСТИТЬ ВТ_Исходная
    |ИЗ
    |    &ТЗ КАК ТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_Исходная.ОргИНН КАК ИНН,
    |    ВТ_Исходная.ОргКПП КАК КПП
    |ПОМЕСТИТЬ ПоИНН
    |ИЗ
    |    ВТ_Исходная КАК ВТ_Исходная
    |ГДЕ
    |    ВТ_Исходная.КонВыгр В (2, 4)
    |
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    |    ВТ_Исходная.КонтрИНН,
    |    ВТ_Исходная.КонтрКПП
    |ИЗ
    |    ВТ_Исходная КАК ВТ_Исходная
    |ИНДЕКСИРОВАТЬ ПО
    |    ИНН,
    |    КПП
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Контрагенты.Ссылка КАК Ссылка,
    |    Контрагенты.ИНН КАК ИНН,
    |    Контрагенты.КПП КАК КПП,
    |    ВЫБОР
    |        КОГДА Контрагенты.ПометкаУдаления
    |            ТОГДА 999
    |        ИНАЧЕ 1
    |    КОНЕЦ КАК Приоритет
    |ИЗ
    |    Справочник.Контрагенты КАК Контрагенты
    |        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПоИНН КАК Допы
    |        ПО Контрагенты.ИНН = Допы.ИНН
    |            И Контрагенты.КПП = Допы.КПП
    |УПОРЯДОЧИТЬ ПО
    |    Приоритет";

Запрос.УстановитьПараметр("ТЗ", ИсходнаяТаблицаЗначений);
РезультатЗапроса = Запрос.Выполнить();

Обратим внимание на важные детали в этом коде:

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

Способ 2. Предварительная подготовка данных во встроенном языке

Рассмотрим альтернативную ситуацию: когда исходная таблица значений формируется программно и логику объединения удобнее вынести из текста запроса в код 1С. В таком случае мы можем заранее собрать единую таблицу нужной структуры.

Разберем по шагам создание и заполнение результирующей таблицы:


// Создаем типизированную таблицу для передачи в запрос
ТЗ_ДляЗапроса = Новый ТаблицаЗначений;
ТЗ_ДляЗапроса.Колонки.Добавить("ИНН", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(12)));
ТЗ_ДляЗапроса.Колонки.Добавить("КПП", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(9)));

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

// В запросе отпадает необходимость объединения
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЗ.ИНН КАК ИНН,
    |    ТЗ.КПП КАК КПП
    |ПОМЕСТИТЬ ПоИНН
    |ИЗ
    |    &ТЗ КАК ТЗ
    |ИНДЕКСИРОВАТЬ ПО
    |    ИНН,
    |    КПП
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Контрагенты.Ссылка КАК Ссылка,
    |    Контрагенты.ИНН КАК ИНН,
    |    Контрагенты.КПП КАК КПП,
    |    ВЫБОР
    |        КОГДА Контрагенты.ПометкаУдаления
    |            ТОГДА 999
    |        ИНАЧЕ 1
    |    КОНЕЦ КАК Приоритет
    |ИЗ
    |    Справочник.Контрагенты КАК Контрагенты
    |        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПоИНН КАК Допы
    |        ПО Контрагенты.ИНН = Допы.ИНН
    |            И Контрагенты.КПП = Допы.КПП
    |УПОРЯДОЧИТЬ ПО
    |    Приоритет";

Запрос.УстановитьПараметр("ТЗ", ТЗ_ДляЗапроса);
РезультатЗапроса = Запрос.Выполнить();

Важные рекомендации при работе с временными таблицами

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

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