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