При написании сложных запросов с передачей внешней таблицы значений (параметр &ТЗ) разработчики часто сталкиваются с синтаксической ошибкой при попытке применить конструкцию ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ непосредственно к источнику-параметру.
Проанализируем типичный пример ошибочного кода, выясним архитектурные причины возникновения проблемы в платформе 1С:Предприятие и рассмотрим правильные варианты реализации задачи.
Посмотрим на исходный запрос, в котором возникает ошибка:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.ОргИНН КАК ИНН,
| ТЗ.ОргКПП КАК КПП
|ПОМЕСТИТЬ ПоИНН
|ИЗ
| &ТЗ КАК ТЗ
|ГДЕ
| (ТЗ.КонВыгр = 2) Или (ТЗ.КонВыгр = 4)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ТЗ.КонтрИНН КАК ИНН,
| ТЗ.КонтрКПП КАК КПП
|ИЗ
| &ТЗ КАК ТЗ
|;
|ВЫБРАТЬ ...";
Выясним причину такого поведения платформы. В языке запросов 1С при работе с внешними наборами данных, переданными через параметры (объект ТаблицаЗначений, табличные части), действуют строгие ограничения. К таблице-параметру нельзя напрямую применять следующие операции:
ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ.СОЕДИНЕНИЕ).СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ.ИТОГИ.АВТОУПОРЯДОЧИВАНИЕ.Единственное доступное действие с внешним источником данных — напрямую выгрузить его строки во временную таблицу базы данных с помощью конструкции ПОМЕСТИТЬ. Только после того, как данные физически окажутся во временной таблице СУБД, с ними можно выполнять любые стандартные реляционные операции: соединять, группировать и объединять.
Разберем подробнее два надежных способа решения поставленной задачи.
Рассмотрим наиболее оптимальный с точки зрения производительности вариант. Вместо многократного чтения таблицы значений мы передаем все необходимые колонки во временную таблицу за один простой запрос, а затем выполняем объединение выборок уже из созданной временной таблицы.
Разберем реализацию по шагам:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.ОргИНН КАК ОргИНН,
| ТЗ.ОргКПП КАК ОргКПП,
| ТЗ.КонтрИНН КАК КонтрИНН,
| ТЗ.КонтрКПП КАК КонтрКПП,
| ТЗ.КонВыгр КАК КонВыгр
|ПОМЕСТИТЬ ВТ_Исходная
|ИЗ
| &ТЗ КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Исходная.ОргИНН КАК ИНН,
| ВТ_Исходная.ОргКПП КАК КПП
|ПОМЕСТИТЬ ПоИНН
|ИЗ
| ВТ_Исходная КАК ВТ_Исходная
|ГДЕ
| ВТ_Исходная.КонВыгр В (2, 4)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ВТ_Исходная.КонтрИНН,
| ВТ_Исходная.КонтрКПП
|ИЗ
| ВТ_Исходная КАК ВТ_Исходная
|ИНДЕКСИРОВАТЬ ПО
| ИНН,
| КПП
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.ИНН КАК ИНН,
| Контрагенты.КПП КАК КПП,
| ВЫБОР
| КОГДА Контрагенты.ПометкаУдаления
| ТОГДА 999
| ИНАЧЕ 1
| КОНЕЦ КАК Приоритет
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПоИНН КАК Допы
| ПО Контрагенты.ИНН = Допы.ИНН
| И Контрагенты.КПП = Допы.КПП
|УПОРЯДОЧИТЬ ПО
| Приоритет";
Запрос.УстановитьПараметр("ТЗ", ИсходнаяТаблицаЗначений);
РезультатЗапроса = Запрос.Выполнить();
Обратим внимание на важные детали в этом коде:
&ТЗ считывается без логических условий и сложных выражений, просто помещая сырые данные в ВТ_Исходная.ОБЪЕДИНИТЬ ВСЕ и фильтрацию ГДЕ ВТ_Исходная.КонВыгр В (2, 4), так как источником выступает уже созданная таблица ВТ_Исходная.ИНДЕКСИРОВАТЬ ПО ИНН, КПП. Так как в дальнейшем по этим полям выполняется ВНУТРЕННЕЕ СОЕДИНЕНИЕ со справочником Контрагенты, наличие индекса существенно ускорит выполнение запроса на сервере СУБД.Рассмотрим альтернативную ситуацию: когда исходная таблица значений формируется программно и логику объединения удобнее вынести из текста запроса в код 1С. В таком случае мы можем заранее собрать единую таблицу нужной структуры.
Разберем по шагам создание и заполнение результирующей таблицы:
// Создаем типизированную таблицу для передачи в запрос
ТЗ_ДляЗапроса = Новый ТаблицаЗначений;
ТЗ_ДляЗапроса.Колонки.Добавить("ИНН", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(12)));
ТЗ_ДляЗапроса.Колонки.Добавить("КПП", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(9)));
// Заполняем данные по условию
Для Каждого Стр Из ИсходнаяТаблицаЗначений Цикл
Если Стр.КонВыгр = 2 Или Стр.КонВыгр = 4 Тогда
НоваяСтр = ТЗ_ДляЗапроса.Добавить();
НоваяСтр.ИНН = Стр.ОргИНН;
НоваяСтр.КПП = Стр.ОргКПП;
КонецЕсли;
// Добавляем вторую ветку объединения
НоваяСтрКонтр = ТЗ_ДляЗапроса.Добавить();
НоваяСтрКонтр.ИНН = Стр.КонтрИНН;
НоваяСтрКонтр.КПП = Стр.КонтрКПП;
КонецЦикла;
// В запросе отпадает необходимость объединения
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.ИНН КАК ИНН,
| ТЗ.КПП КАК КПП
|ПОМЕСТИТЬ ПоИНН
|ИЗ
| &ТЗ КАК ТЗ
|ИНДЕКСИРОВАТЬ ПО
| ИНН,
| КПП
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.ИНН КАК ИНН,
| Контрагенты.КПП КАК КПП,
| ВЫБОР
| КОГДА Контрагенты.ПометкаУдаления
| ТОГДА 999
| ИНАЧЕ 1
| КОНЕЦ КАК Приоритет
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПоИНН КАК Допы
| ПО Контрагенты.ИНН = Допы.ИНН
| И Контрагенты.КПП = Допы.КПП
|УПОРЯДОЧИТЬ ПО
| Приоритет";
Запрос.УстановитьПараметр("ТЗ", ТЗ_ДляЗапроса);
РезультатЗапроса = Запрос.Выполнить();
Проанализируем нюансы, которые необходимо учитывать при передаче коллекций в запрос:
ОписаниеТипов. Если хотя бы одна колонка содержит неопределенный или составной тип с пустыми ссылками, платформа выдаст ошибку времени выполнения.ОБЪЕДИНИТЬ ВСЕ дублирующиеся строки не удаляются. Если одна и та же пара ИНН и КПП встретится несколько раз, внутреннее соединение приведет к умножению записей справочника Контрагенты в итоговой выборке. Если задвоение недопустимо, следует использовать ключевое слово ОБЪЕДИНИТЬ (без слова ВСЕ), либо сгруппировать записи во временной таблице через СГРУППИРОВАТЬ ПО.