При разработке отчетов, обработок и процедур выгрузки данных (например, в форматы XML или JSON) разработчики часто сталкиваются с необходимостью одновременного получения реквизитов шапки объекта и строк его табличной части. Платформа 1С:Предприятие предоставляет для этого специальный синтаксис обращения к табличной части через точку со скобками.
Рассмотрим, является ли такой подход корректным, как он транслируется на уровне СУБД, к каким скрытым проблемам с производительностью может привести и какие существуют альтернативные способы решения этой задачи.
Разберем пример запроса, в котором поля табличной части выбираются непосредственно через имя реквизита-коллекции:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка,
| Спр.Поле1 КАК Поле1,
| Спр.Поле2 КАК Поле2,
| Спр.ТЧ1.(
| ПолеТч1,
| ПолеТч2
| ) КАК СоставТЧ
|ИЗ
| Справочник.Номенклатура КАК Спр";
Выборка = Запрос.Выполнить().Выбрать();
Данная конструкция полностью поддерживается языком запросов 1С. В результате выполнения такого запроса в основном наборе данных поле СоставТЧ будет содержать отдельный объект РезультатЗапроса для каждой строки шапки.
Проанализируем, что происходит на сервере при выполнении запроса с вложенной таблицей:
LEFT OUTER JOIN) таблицы шапки с таблицей табличной части. Сервер баз данных возвращает плоский результирующий набор строк, в котором поля шапки дублируются для каждой строки табличной части.Выгрузить(), как в примере Выборка.СоставТЧ.Выгрузить(), платформа на каждой итерации будет создавать новый объект ТаблицаЗначений. При обработке сотен тысяч записей это приводит к лавинообразному расходу оперативной памяти и существенной нагрузке на сборщик мусора.Если мы выбираем этот способ, обход вложенных данных необходимо выполнять потоковым методом через Выбрать(), не создавая промежуточных таблиц значений в памяти:
Пока Выборка.Следующий() Цикл
// Обрабатываем поля шапки
ЗначениеПоля1 = Выборка.Поле1;
// Потоковый обход подчиненных записей без выгрузки в ТаблицуЗначений
ВыборкаТЧ = Выборка.СоставТЧ.Выбрать();
Пока ВыборкаТЧ.Следующий() Цикл
ЗначениеТЧ = ВыборкаТЧ.ПолеТч1;
КонецЦикла;
КонецЦикла;
Выясним, с какими жесткими ограничениями платформы можно столкнуться при использовании синтаксиса вложенных таблиц:
ВЫБРАТЬ, нельзя поместить во временную таблицу с помощью конструкции ПОМЕСТИТЬ.ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ.ГДЕ, СГРУППИРОВАТЬ ПО, ИМЕЮЩИЕ, а также в секции УПОРЯДОЧИТЬ ПО основного запроса.ИЗ или ГДЕ.Рассмотрим основные паттерны, которые применяются в профессиональной разработке взамен синтаксиса вложенных таблиц.
Это стандартный и наиболее производительный подход при формировании структурированных файлов выгрузки (XML, JSON). Платформа оптимизирует чтение данных потоком без создания лишних объектов в памяти.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка,
| Спр.Поле1 КАК Поле1,
| ТЧ.ПолеТч1 КАК ПолеТч1,
| ТЧ.ПолеТч2 КАК ПолеТч2
|ИЗ
| Справочник.Номенклатура КАК Спр
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ТЧ1 КАК ТЧ
| ПО Спр.Ссылка = ТЧ.Ссылка
|ИТОГИ
| МАКСИМУМ(Поле1)
|ПО
| Ссылка";
Результат = Запрос.Выполнить();
ВыборкаШапка = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаШапка.Следующий() Цикл
// Обработка данных шапки
СсылкаЭлемента = ВыборкаШапка.Ссылка;
// Обход детальных записей табличной части
ВыборкаСтроки = ВыборкаШапка.Выбрать();
Пока ВыборкаСтроки.Следующий() Цикл
Если ЗначениеЗаполнено(ВыборкаСтроки.ПолеТч1) Тогда
// Обработка строк табличной части
КонецЕсли;
КонецЦикла;
КонецЦикла;
Если из шапки требуется только ссылка или минимальный набор полей, запрос можно адресовать непосредственно к таблице табличной части, упорядочив результат по ссылке:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Ссылка КАК Ссылка,
| ТЧ.Ссылка.Поле1 КАК Поле1,
| ТЧ.ПолеТч1 КАК ПолеТч1,
| ТЧ.ПолеТч2 КАК ПолеТч2
|ИЗ
| Справочник.Номенклатура.ТЧ1 КАК ТЧ
|УПОРЯДОЧИТЬ ПО
| ТЧ.Ссылка,
| ТЧ.НомерСтроки";
Выборка = Запрос.Выполнить().Выбрать();
ТекущаяСсылка = Неопределено;
Пока Выборка.Следующий() Цикл
Если ТекущаяСсылка <> Выборка.Ссылка Тогда
ТекущаяСсылка = Выборка.Ссылка;
// Выполняем действия при переходе к новому объекту (заголовок узла XML)
КонецЕсли;
// Выполняем обработку строки табличной части
КонецЦикла;
Когда требуется сложная обработка больших массивов с предварительной фильтрацией, оптимально использовать пакетные запросы. Сначала во временную таблицу отбираются ссылки шапки, а затем отдельными запросами пакета получаются данные шапок и данные табличных частей с соединением по временной таблице:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_ОтобранныеСсылки
|ИЗ
| Справочник.Номенклатура КАК Спр
|ГДЕ
| Спр.ПометкаУдаления = ЛОЖЬ
|ИНДЕКСИРОВАТЬ ПО
| Ссылка;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка,
| Спр.Поле1 КАК Поле1
|ИЗ
| ВТ_ОтобранныеСсылки КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Спр
| ПО ВТ.Ссылка = Спр.Ссылка;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЧ.Ссылка КАК Ссылка,
| ТЧ.ПолеТч1 КАК ПолеТч1,
| ТЧ.ПолеТч2 КАК ПолеТч2
|ИЗ
| ВТ_ОтобранныеСсылки КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ТЧ1 КАК ТЧ
| ПО ВТ.Ссылка = ТЧ.Ссылка
|УПОРЯДОЧИТЬ ПО
| ТЧ.Ссылка,
| ТЧ.НомерСтроки";
ПакетРезультатов = Запрос.ВыполнитьПакет();
ТЗ_Шапки = ПакетРезультатов[1].Выгрузить();
ТЗ_Строки = ПакетРезультатов[2].Выгрузить();
Подведем итог и определим, когда допустимо использовать выборку через вложенные таблицы:
Спр.ТЧ1.(...) допустимо: в простых сервисных обработках, небольших разовых выгрузках и сценариях, где объем обрабатываемых данных невелик и нет потребности в сложных фильтрах или помещении данных во временные таблицы. Обязательным условием является отказ от метода Выгрузить() в пользу потокового метода Выбрать().ИТОГИ ПО рекомендуется: во всех задачах массовой потоковой выгрузки данных, генерации отчетов и формирования XML/JSON-документов большого объема. Этот подход обеспечивает оптимальную скорость работы и минимальное потребление оперативной памяти.