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