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

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

В практике разработки на платформе «1С:Предприятие 8» часто возникает задача отображения связей между документами. Например, когда один документ-заказ ссылается на другой аналогичный заказ (корректирующий или подчиненный). При этом стандартный механизм критериев отбора не всегда применим или удобен, особенно если нужно получить готовую коллекцию данных в виде дерева непосредственно в коде или вывести ее на форму.

В данной статье мы подробно разберем, как решить эту задачу с помощью механизмов платформы. Мы проанализируем, почему стандартная выгрузка запроса создает «лишние» строки с NULL для документов без подчиненных, и рассмотрим несколько эффективных способов построения чистого иерархического дерева.

Постановка задачи и основная техническая сложность

Представим, что у нас есть документ Документ.БЗ_ЗаявкаПокупателя. В этом документе есть реквизит ОсновнаяЗаявка, указывающий на родительский документ того же типа. Нам требуется построить дерево следующего вида:

Главная сложность заключается в том, что при использовании обычного ЛЕВОГО СОЕДИНЕНИЯ документов «самих на себя» для документов без подчиненных СУБД возвращает строку, в которой правая часть соединения содержит значение NULL. Если просто выгрузить результат такого запроса в ДеревоЗначений методом РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам), платформа автоматически создаст подчиненную пустую строку под каждым «одиночным» родителем. Наша цель — избавиться от этих пустых строк.

Давайте пошагово разберем три основных способа решения этой задачи.

---

Способ 1. Запрос с конструкцией ИТОГИ ПО и программный обход выборки

Этот способ является наиболее гибким, надежным и часто используемым на практике. Мы пишем запрос с левым соединением, группируем результаты с помощью предложения ИТОГИ ПО, а затем обходим выборку программно. При обходе мы проверяем наличие подчиненных документов и добавляем строки в дерево только тогда, когда подчиненный документ реально существует (не равен NULL).

Рассмотрим структуру запроса и код заполнения дерева значений на форме:


// 1. Создаем запрос для получения связей документов
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	БЗ_ЗаявкаПокупателя.Ссылка КАК Заявка,
    |	БЗ_ПодчиненныеЗаявки.Ссылка КАК ЗаявкаПодчиненная
    |ИЗ
    |	Документ.БЗ_ЗаявкаПокупателя КАК БЗ_ЗаявкаПокупателя
    |		ЛЕВОЕ СОЕДИНЕНИЕ Документ.БЗ_ЗаявкаПокупателя КАК БЗ_ПодчиненныеЗаявки
    |		ПО БЗ_ЗаявкаПокупателя.Ссылка = БЗ_ПодчиненныеЗаявки.ОсновнаяЗаявка
    |			И (БЗ_ПодчиненныеЗаявки.ВключитьвЗаявку)
    |ГДЕ
    |	НЕ БЗ_ЗаявкаПокупателя.ВключитьвЗаявку
    |	И БЗ_ЗаявкаПокупателя.ОсновнаяЗаявка = ЗНАЧЕНИЕ(Документ.БЗ_ЗаявкаПокупателя.ПустаяСсылка)
    |ИТОГИ ПО
    |	Заявка";

РезультатЗапроса = Запрос.Выполнить();

// 2. Получаем коллекцию ДеревоЗначений из реквизита управляемой формы
ДеревоЗначений = РеквизитФормыВЗначение("СписокДокументов");
ДеревоЗначений.Строки.Очистить();

// 3. Обходим выборку первого уровня (Родительские документы)
ВыборкаСсылка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаСсылка.Следующий() Цикл
    
    // Добавляем родительскую строку в дерево
    Корень = ДеревоЗначений.Строки.Добавить();
    Корень.Заявка = ВыборкаСсылка.Заявка;
    
    // Получаем детальные записи (подчиненные документы)
    ВыборкаДетальныеЗаписи = ВыборкаСсылка.Выбрать();
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        
        // Важная проверка: исключаем пустые строки (NULL)
        Если ВыборкаДетальныеЗаписи.ЗаявкаПодчиненная <> NULL Тогда
            ПодчиненаяСтрока = Корень.Строки.Добавить();
            ПодчиненаяСтрока.Заявка = ВыборкаДетальныеЗаписи.ЗаявкаПодчиненная;
        КонецЕсли;
        
    КонецЦикла;
    
КонецЦикла;

// 4. Возвращаем результат обратно в реквизит формы для отображения на экране
ЗначениеВРеквизитФормы(ДеревоЗначений, "СписокДокументов");

Преимущество метода: Мы получаем гарантированно чистое дерево без лишних пустых веток. Метод работает быстро и прозрачно.

---

Способ 2. Использование Временных таблиц и объединения (Оптимизация для больших баз)

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

Проанализируем этот алгоритм:

  1. Создаем временную таблицу ВТ_Родители, куда помещаем только корневые документы (у которых реквизит ОсновнаяЗаявка пустой).
  2. Во втором запросе пакета выбираем подчиненные документы, делая внутреннее соединение с таблицей ВТ_Родители.
  3. С помощью оператора ОБЪЕДИНИТЬ ВСЕ соединяем выборку реально существующих подчиненных пар и список родителей, у которых подчиненных нет (подставляя вместо подчиненного значения NULL).

Пример реализации пакета запросов:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Док.Ссылка КАК Ссылка
    |ПОМЕСТИТЬ ВТ_Родители
    |ИЗ
    |	Документ.БЗ_ЗаявкаПокупателя КАК Док
    |ГДЕ
    |	Док.ОсновнаяЗаявка = ЗНАЧЕНИЕ(Документ.БЗ_ЗаявкаПокупателя.PushСсылка)
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_Родители.Ссылка КАК Заявка,
    |	Подчиненные.Ссылка КАК ЗаявкаПодчиненная
    |ИЗ
    |	ВТ_Родители КАК ВТ_Родители
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.БЗ_ЗаявкаПокупателя КАК Подчиненные
    |		ПО ВТ_Родители.Ссылка = Подчиненные.ОсновнаяЗаявка
    |
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    |	ВТ_Родители.Ссылка,
    |	NULL
    |ИЗ
    |	ВТ_Родители КАК ВТ_Родители
    |УПОРЯДОЧИТЬ ПО
    |	Заявка.Дата,
    |	ЕСТЬNULL(ЗаявкаПодчиненная.Дата, ДАТАВРЕМЯ(1, 1, 1))
    |ИТОГИ ПО
    |	Заявка";

Такой подход обеспечивает высокую скорость работы СУБД за счет использования индексов временной таблицы при соединении. Обход результата запроса в коде выполняется аналогично первому способу.

---

Способ 3. Построение дерева без написания кода через Систему компоновки данных (СКД)

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

Разберем, как настроить СКД для решения нашей задачи:

  1. Создаем новый макет схемы компоновки данных.
  2. Добавляем набор данных - запрос, который выбирает все документы:
    
    ВЫБРАТЬ
        Док.Ссылка КАК Ссылка,
        Док.ОсновнаяЗаявка КАК ОсновнаяЗаявка
    ИЗ
        Документ.БЗ_ЗаявкаПокупателя КАК Док
    
  3. Переходим на вкладку «Связи наборов данных». Добавляем новую связь:
    • Источник связи: Наш набор данных.
    • Приемник связи: Он же (тот же самый набор данных).
    • Выражение источника: Ссылка.
    • Выражение приемника: ОсновнаяЗаявка.
    • Начальное условие связи: ОсновнаяЗаявка ИЗЧИСЛА ПустаяСсылка или равенство пустой ссылке (это определит корни дерева).
  4. На вкладке «Настройки» добавляем структуру типа «Группировка» по полю Ссылка. На вкладке «Другие настройки» указываем тип макета — «Дерево».

Если вам нужно получить результат работы СКД в виде переменной ДеревоЗначений в программном коде модуля формы, используйте следующий шаблон:


// Инициализируем схему и компоновщик настроек
СхемаКомпоновкиДанных = ПолучитьМакет("СхемаКомпоновкиДанных");
Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;

// Компонуем макет
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки,,, Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));

// Проводим компоновку данных
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);

// Выводим результат непосредственно в Дерево Значений
ДеревоРезультата = Новый ДеревоЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(ДеревоРезультата);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

// На выходе получаем чистое иерархическое дерево без пустых веток!

---

Резюме и рекомендации

Мы рассмотрели три мощных инструмента для связывания плоских таблиц документов в иерархическую структуру. Какой из них выбрать?

← На главную