В разработке на платформе 1С:Предприятие часто возникает задача получения иерархических структур данных. Классический пример — запрос к документу, где мы можем одной строкой получить ссылку на сам документ и вложенную в ячейку таблицу его товаров. Это выглядит удобно и лаконично:
ВЫБРАТЬ
ПоступлениеТоваровНаСклад.Ссылка КАК Ссылка,
ПоступлениеТоваровНаСклад.Товары.(
Номенклатура КАК Номенклатура,
Количество КАК Количество
) КАК Товары
ИЗ
Документ.ПоступлениеТоваровНаСклад КАК ПоступлениеТоваровНаСклад
Однако, как только мы пытаемся применить этот же синтаксис к временным таблицам или результатам соединений (не являющихся физическими табличными частями объектов метаданных), платформа выдает ошибку. В этой статье мы разберем, почему так происходит, и рассмотрим эффективные способы обхода этого ограничения для формирования иерархических структур, например, для выгрузки в JSON.
Проанализируем ситуацию: синтаксис Поле.(ВложенноеПоле1, ВложенноеПоле2) жестко зашит в парсере языка запросов 1С. Этот механизм работает исключительно для объектов, имеющих тип Табличная часть. Платформа на уровне СУБД выполняет оптимизированное соединение или несколько запросов, самостоятельно упаковывая результат в объект типа РезультатЗапроса внутри ячейки конкретной строки.
Для временных таблиц, созданных внутри запроса, или для результатов левого соединения двух обычных таблиц, типа «Таблица значений» внутри ячейки в языке запросов не существует. Поэтому нам приходится использовать альтернативные методы формирования иерархии.
Если нашей целью является максимальная производительность (например, формирование остатков по складам для HTTP-сервиса), рассмотрим метод «плоской» выборки с использованием двух указателей. Основная идея заключается в том, чтобы получить два набора данных, упорядоченных по одному и тому же ключу (например, по Номенклатуре), и обходить их синхронно.
Разберем реализацию этого метода по шагам:
УПОРЯДОЧИТЬ ПО для обоих запросов по ключевому полю.Посмотрим на пример кода:
// Предположим, у нас есть два результата запроса: РезультатыЗапросаОстатки и РезультатыЗапросаДетально
ВыборкаШапка = РезультатыЗапросаОстатки.Выбрать();
ВыборкаДетали = РезультатыЗапросаДетально.Выбрать();
// Позиционируем детальную выборку на первую строку заранее
Если ВыборкаДетали.Следующий() Тогда
ЕстьДанныеДеталей = Истина;
Иначе
ЕстьДанныеДеталей Ложь;
КонецЕсли;
Пока ВыборкаШапка.Следующий() Цикл
// Записываем данные основного объекта (например, в структуру для JSON)
НоваяСтрокаJSON = Новый Структура("Товар, Остаток, Склады", ВыборкаШапка.Номенклатура, ВыборкаШапка.ОстатокОбщий, Новый Массив);
// Вложенный цикл: пока ключи совпадают, добавляем детали
Пока ЕстьДанныеДеталей И ВыборкаДетали.Номенклатура = ВыборкаШапка.Номенклатура Цикл
ДанныеСклада = Новый Структура("Склад, Остаток", ВыборкаДетали.Склад, ВыборкаДетали.Остаток);
НоваяСтрокаJSON.Склады.Добавить(ДанныеСклада);
// Переходим к следующей строке в деталях
Если НЕ ВыборкаДетали.Следующий() Тогда
ЕстьДанныеДеталей = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
// Здесь можно сериализовать НоваяСтрокаJSON в поток
КонецЦикла;
Этот метод является наиболее производительным, так как требует всего одного прохода по данным без создания тяжелых промежуточных объектов ТаблицаЗначений в каждой строке.
Если мы хотим использовать возможности самого языка запросов для группировки данных, воспользуемся предложением ИТОГИ. Проанализируем ситуацию: когда мы добавляем итоги в запрос, мы можем получить иерархическую выборку с помощью метода Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
Рассмотрим пример запроса:
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.Склад КАК Склад,
Остатки.ВНаличии КАК Остаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ИТОГИ
СУММА(Остаток)
ПО
Номенклатура
Теперь разберем, как программно получить вложенные таблицы для каждой номенклатуры:
ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНоменклатура.Следующий() Цикл
// В этот момент ВыборкаНоменклатура.Остаток содержит сумму по всем складам
// Чтобы получить "таблицу значений" складов для этой номенклатуры:
ВыборкаСклады = ВыборкаНоменклатура.Выбрать();
// Если вам все же нужна именно ТаблицаЗначений, можно использовать Выгрузить()
ТЗДеталей = ВыборкаСклады.Выгрузить();
// Обрабатываем ТЗДеталей...
КонецЦикла;
Этот способ удобен тем, что 1С сама берет на себя сопоставление данных, но он чуть медленнее предыдущего из-за накладных расходов на создание объектов выборки и выгрузку таблиц.
Система компоновки данных умеет работать со «Вложенными наборами данных». Это единственный штатный механизм, который позволяет связать два произвольных запроса и получить на выходе иерархическую структуру (например, ДеревоЗначений).
Рассмотрим, как это работает:
Номенклатура).Выполнив такую схему программно, вы можете выгрузить результат в дерево, где подчиненные строки каждой ветки и будут представлять собой ту самую «вложенную таблицу».
Выясним, какой метод лучше использовать в зависимости от задачи:
ЗаписьJSON по мере обхода, минимизируя потребление оперативной памяти (не нужно хранить всё дерево или массив ТЗ в памяти).Таким образом, хотя платформа 1С не позволяет напрямую «конструировать» таблицы значений внутри ячеек произвольного запроса, комбинация правильного упорядочивания и иерархического обхода выборок полностью решает эту задачу.