При проектировании сложных отчетов или разработке механизмов проведения документов в системе «1С:Предприятие» программисты часто используют временные таблицы для оптимизации и структурирования запросов. Однако при попытке поместить данные табличной части документа или справочника во временную таблицу конструктор запросов выдает сообщение об ошибке: «В запросе, формирующем временную таблицу, не могут выбираться вложенные таблицы...». В этой статье мы подробно разберем технические причины возникновения данного ограничения и рассмотрим все доступные способы его обхода — от классических методов до современных возможностей платформы.
Прежде чем перейти к практическим решениям, проанализируем техническую суть проблемы. Когда мы пишем запрос в 1С и выбираем табличную часть через точку от объекта (например, Акт.ПоДаннымОрганизации.(Номенклатура, Сумма)), платформа 1С пытается вернуть нам структуру со вложенным результатом запроса. То есть одна строка результата будет содержать в себе целую подчиненную таблицу.
Однако временные таблицы физически создаются непосредственно на стороне СУБД (например, в базе TempDB на Microsoft SQL Server или во временном табличном пространстве PostgreSQL). Реляционные СУБД по своей природе спроектированы для работы с плоскими двумерными таблицами. Они физически не поддерживают тип данных «вложенная таблица» внутри ячейки строки. Каждая колонка временной таблицы обязана иметь простой, строго определенный тип: число, строка, дата, булево или ссылка (которая на уровне СУБД представляет собой уникальный идентификатор GUID). Из-за этого непреодолимого архитектурного ограничения СУБД платформа 1С накладывает жесткий запрет на использование конструкции ПОМЕСТИТЬ в запросах, содержащих вложенные таблицы.
Самый простой и производительный способ обойти это ограничение в запросе — отказаться от выборки вложенной таблицы через объект и обратиться к физической таблице табличной части напрямую. Это позволяет автоматически «развернуть» структуру в плоский вид, который без проблем запишется во временную таблицу.
Давайте посмотрим на конкретный пример. Предположим, у нас есть следующий нерабочий запрос:
ВЫБРАТЬ
Акт.Ссылка КАК Ссылка,
Акт.ПоДаннымОрганизации.(
Номенклатура,
Сумма
) КАК ТабличнаяЧасть
ПОМЕСТИТЬ ВТ_ДанныеАкта
ИЗ
Документ.АктСверкиВзаиморасчетов КАК Акт
ГДЕ
Акт.Ссылка = &Ссылка
Этот запрос вызовет ошибку компиляции. Чтобы исправить его, мы должны переписать обращение напрямую к табличной части ПоДаннымОрганизации этого документа. Измененный и абсолютно корректный запрос будет выглядеть следующим образом:
ВЫБРАТЬ
ТЧ.Ссылка КАК Ссылка,
ТЧ.Номенклатура КАК Номенклатура,
ТЧ.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_ДанныеАкта
ИЗ
Документ.АктСверкиВзаиморасчетов.ПоДаннымОрганизации КАК ТЧ
ГДЕ
ТЧ.Ссылка = &Ссылка
В данном случае мы выбираем плоские строки из физической таблицы Документ.АктСверкиВзаиморасчетов.ПоДаннымОрганизации. Каждая строка содержит ссылку на документ-владелец и простые типы данных в колонках. СУБД легко создает такую временную таблицу, и ошибка больше не возникает.
Что делать, если документ, табличную часть которого нам нужно обработать во временной таблице, еще не записан в базу данных? Например, пользователь только что заполнил форму на клиенте, нажал кнопку «Рассчитать», и нам требуется произвести сложные вычисления в запросе без предварительного сохранения объекта в БД.
В этой ситуации прямое обращение к таблице Документ...ТабличнаяЧасть не вернет новые данные, так как в базе данных их еще просто нет. Решением является выгрузка табличной части в Таблицу Значений на сервере и последующая передача этой таблицы в запрос в качестве параметра.
Рассмотрим важный нюанс, о котором часто забывают начинающие разработчики: колонки передаваемой Таблицы Значений должны быть строго типизированы. Если мы выгружаем ТЧ стандартным методом Выгрузить(), типы колонок наследуются из метаданных документа автоматически. Но если мы создаем или изменяем ТЗ вручную в коде, мы обязаны явно задать ОписаниеТипов для каждой колонки. Иначе платформа выдаст другую ошибку: «Тип не может быть выбран в запросе».
Разберем по шагам пример реализации этого подхода в коде:
// Шаг 1. Получаем таблицу значений из табличной части незаписанного документа
ТаблицаДанных = Объект.ПоДаннымОрганизации.Выгрузить();
// Шаг 2. Создаем запрос и менеджер временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Шаг 3. Передаем таблицу значений как параметр запроса
Запрос.УстановитьПараметр("ВнешнийИсточникТЧ", ТаблицаДанных);
// Шаг 4. Помещаем данные во временную таблицу и производим дальнейшие расчеты
Запрос.Текст =
"ВЫБРАТЬ
| ВнешнийИсточник.Номенклатура КАК Номенклатура,
| ВнешнийИсточник.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_ДанныеАкта
|ИЗ
| &ВнешнийИсточникТЧ КАК ВнешнийИсточник;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура,
| СУММА(ВТ.Сумма) КАК ОбщаяСумма
|ИЗ
| ВТ_ДанныеАкта КАК ВТ
|СГРУППИРОВАТЬ ПО
| ВТ.Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Этот способ крайне гибок, так как позволяет производить любые предварительные манипуляции с данными в коде перед их отправкой на обработку в СУБД.
Если задача решается в рамках Системы Компоновки Данных (СКД), использование временных таблиц для незаписанных данных может быть излишне сложным. В СКД для этих целей идеально подходит механизм под названием «Набор данных - объект». Он позволяет передать произвольную таблицу значений напрямую в компоновщик макета.
Проанализируем алгоритм реализации этого метода:
ТаблицаАкта.Номенклатура с типом СправочникСсылка.Номенклатура и Сумма с типом Число).ПриКомпоновкеРезультата) выполните программную инициализацию процессора компоновки и передайте выгруженную таблицу значений в структуру внешних наборов данных.Посмотрим на пример программного кода в модуле отчета:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Получаем таблицу значений из реквизитов формы или объекта
ТаблицаДляСКД = ЭтотОбъект.ПолучитьТабличнуюЧастьАкта();
// Создаем структуру для передачи внешних наборов данных
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаАкта", ТаблицаДляСКД);
// Выполняем стандартную инициализацию компоновки
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Использование набора данных-объекта избавляет вас от необходимости писать сложные SQL-запросы для ручного развертывания табличных частей и делает разработку отчета по временным или незаписанным данным простой и прозрачной.
Если вы работаете на современной версии платформы «1С:Предприятие» (8.3.25 и выше), вам доступен принципиально новый и невероятно удобный механизм — ключевое слово ДОБАВИТЬ (аналог SQL-конструкции ADD во временных таблицах).
Раньше, если требовалось собрать воедино данные из нескольких разных незаписанных ТЧ (или табличных частей разных документов) в одну общую временную таблицу, разработчикам приходилось конструировать громоздкие запросы с множественными операторами ОБЪЕДИНИТЬ ВСЕ в рамках одного пакета запросов.
Теперь мы можем наполнять созданную временную таблицу постепенно, шаг за шагом добавляя в нее данные из разных источников. Рассмотрим, как это выглядит на практике:
// Шаг 1. Создаем временную таблицу на основе первой ТЗ
ВЫБРАТЬ
ТЧ1.Номенклатура КАК Номенклатура,
ТЧ1.Количество КАК Количество
ПОМЕСТИТЬ ВТ_СводныеДанные
ИЗ
&ТЗ_ПервыйДокумент КАК ТЧ1;
// Шаг 2. Добавляем данные из второй ТЗ в эту же временную таблицу
ВЫБРАТЬ
ТЧ2.Номенклатура КАК Номенклатура,
ТЧ2.Количество КАК Количество
ДОБАВИТЬ ВТ_СводныеДанные
ИЗ
&ТЗ_ВторойДокумент КАК ТЧ2;
Обратите внимание, что при использовании оператора ДОБАВИТЬ структура полей (названия и типы колонок) в последующих запросах должна строго соответствовать структуре исходной временной таблицы, заданной при её создании (через ПОМЕСТИТЬ). Этот подход существенно упрощает разработку динамических отчетов, модулей расчета себестоимости и других алгоритмов со сложной логикой сбора данных.
Мы подробно проанализировали ограничения СУБД и платформы 1С на выборку вложенных таблиц во временные таблицы и рассмотрели четыре эффективных способа решения этой проблемы. Выбор конкретного метода зависит от архитектуры вашей задачи:
ДОБАВИТЬ для поэтапного наполнения плоских временных таблиц из разных источников (Способ 4).