Как получить коллекцию подчиненных документов в одной колонке результата запроса 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в 1С часто возникает задача, когда необходимо вывести в одну строку данные главного документа, а в отдельной колонке сформировать список или коллекцию всех подчиненных ему документов (документов-оснований). Если мы используем обычное левое соединение в запросе, то строки с главным документом дублируются. Давайте разберем, как получить красивую таблицу, где в поле Документы будет содержаться коллекция ссылок на подчиненные документы.

Важно сразу прояснить ограничения платформы 1С: получить произвольную вложенную коллекцию (которая не является табличной частью) непосредственно в тексте запроса с помощью оператора ВЫБРАТЬ невозможно. Табличные части — это единственная возможность получить вложенную коллекцию в чистых результатах запроса. Для всех остальных случаев мы рассмотрим эффективные обходные пути.

Решение 1: Использование возможностей Системы Компоновки Данных (СКД)

Если конечная цель — вывод данных пользователю или использование механизмов программного формирования отчета, то СКД справляется с этой задачей "из коробки". Разберем доступные функции.

1. Функция Массив()

В выражениях ресурсов СКД мы можем использовать агрегатную функцию Массив(). Она позволяет собрать все сгруппированные значения в настоящий объект платформы.

  1. Создаем простой запрос, связывающий Документ1 и Документ2 через левое соединение.
  2. На вкладке "Ресурсы" добавляем поле Документ2.
  3. В колонке "Выражение" пишем: Массив(Документ2).
  4. Рассчитываем ресурс по группировке Документ1.

При выгрузке результата такого отчета в таблицу значений в колонке ресурсов мы получим объекты типа Массив.

2. Функция СоединитьСтроки()

Если задача заключается только в визуализации списка документов для пользователя, гораздо выгоднее соединить их представления в одну текстовую строку. Для этого в ресурсах СКД используем функцию СоединитьСтроки():

СоединитьСтроки(Документ2, ", ")

Это выражение аккуратно схлопнет все документы через запятую, избавляя нас от необходимости писать циклы перебора записей вручную.

Решение 2: Использование конструкции ИТОГИ в запросе

Если мы работаем вне СКД (например, в модуле объекта при проведении или в серверном коде обработки), рассмотрим классический подход с использованием итогов.

В тексте запроса мы группируем данные по главному документу с помощью секции ИТОГИ. Проанализируем этот алгоритм:

  1. Пишем запрос, соединяющий таблицы.
  2. В конце добавляем ИТОГИ ПО Документ1.
  3. Выполняем запрос и выгружаем его результат в объект ДеревоЗначений с помощью метода Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам).

В результате мы получаем дерево, где на первом (верхнем) уровне располагаются данные главного документа (Дата, Контрагент, Сумма), а на нижнем (подчиненном) уровне — детальные записи с подчиненными документами. Далее дерево можно обойти циклом и собрать из строк нужные коллекции или таблицы.

Решение 3: Оптимизированная постобработка для больших объемов данных

Использование деревьев значений отлично работает на тысячах строк. Но что делать, если в таблице больше 1 миллиона записей? Обход всех строк дерева и суммирование займут неприемлемо большое время, так как работа с вложенными строками дерева в памяти требует значительных ресурсов.

Разберем наиболее производительный метод для огромных массивов данных — сборку плоской таблицы значений и использование объекта Соответствие.

  1. Получаем плоскую выборку данных одним запросом (Документ1, Дата, Контрагент, Сумма, Документ2). В этом запросе нет группировок или итогов.
  2. Выгружаем результат в ТаблицаЗначений.
  3. Создаем Соответствие, которое будет выступать в роли быстрого кэша, где ключ — это ссылка на Документ1, а значение — массив подчиненных документов.
  4. Обходим таблицу один раз и формируем массивы.

Посмотрим на пример реализации данного алгоритма:


// Предполагаем, что РезультатЗапроса - это плоская таблица
ТаблицаДанных = РезультатЗапроса.Выгрузить();
КэшПодчиненных = Новый Соответствие;
ИтоговаяТаблица = Новый ТаблицаЗначений;
ИтоговаяТаблица.Колонки.Добавить("Документ1");
ИтоговаяТаблица.Колонки.Добавить("МассивДокументов2");

Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    
    // Ищем массив в соответствии
    МассивДок2 = КэшПодчиненных.Получить(СтрокаТЗ.Документ1);
    
    Если МассивДок2 = Неопределено Тогда
        // Если документа еще нет, создаем массив и новую строку в итоговой таблице
        МассивДок2 = Новый Массив;
        КэшПодчиненных.Вставить(СтрокаТЗ.Документ1, МассивДок2);
        
        НоваяСтрокаИтогов = ИтоговаяТаблица.Добавить();
        НоваяСтрокаИтогов.Документ1 = СтрокаТЗ.Документ1;
        НоваяСтрокаИтогов.МассивДокументов2 = МассивДок2;
    КонецЕсли;
    
    // Добавляем подчиненный документ в массив, если он не пустой (Null при левом соединении)
    Если ЗначениеЗаполнено(СтрокаТЗ.Документ2) Тогда
        МассивДок2.Добавить(СтрокаТЗ.Документ2);
    КонецЕсли;
    
КонецЦикла;

Проанализируем преимущества такого подхода: поиск по ключу в объекте Соответствие (хэш-таблица) происходит практически мгновенно. Мы обходим выборку из миллионов строк всего за один линейный проход (сложность O(n)). При этом в оперативную память не загружается тяжелая структура дерева, и мы получаем готовую плоскую ТаблицаЗначений, в одной из колонок которой лежат сформированные массивы. Данную таблицу затем можно индексировать или передавать для дальнейшей обработки.

Нюансы управления памятью: Обратите внимание, что хранение миллионов независимых объектов типа Массив в памяти сеанса может потребовать большого объема ОЗУ (особенно в 32-битных рабочих процессах). Если вы работаете с экстремальными объемами, рассмотрите возможность выполнения расчетов порциями или использования временных таблиц базы данных в пакете запросов, где соединения осуществляются лишь в момент финальной записи, минуя конвертацию объектов в память сервера 1С.

← На главную