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