Как посчитать общее количество вложенных элементов для каждой ветки в иерархическом справочнике 1С?

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

В процессе работы с иерархическими справочниками часто возникает задача отобразить в списке не только саму структуру, но и количественные показатели по каждой группе. Стандартная задача — показать количество прямых потомков. Но что если нужно для каждой ветки (группы) вывести общее число всех вложенных в нее элементов на всех уровнях иерархии? При этом решение должно работать в динамическом списке, отображаемом в виде дерева.

Проанализируем несколько подходов к решению этой задачи, от простого запроса до более сложных, но гибких серверных вычислений.

Решение 1: Запрос с множественными соединениями при известной глубине иерархии

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

Посмотрим на пример. Допустим, мы точно знаем, что максимальное количество уровней иерархии в нашем справочнике не превышает шести. Тогда запрос может выглядеть следующим образом:


ВЫБРАТЬ
    Т1.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.Ссылка) + 
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т3.Ссылка) + 
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т4.Ссылка) + 
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т5.Ссылка) + 
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т6.Ссылка) КАК КоличествоВсехПодчиненных
ИЗ
    Справочник.МойСправочник КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.МойСправочник КАК Т2
            ПО Т1.Ссылка = Т2.Родитель
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.МойСправочник КАК Т3
            ПО Т2.Ссылка = Т3.Родитель
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.МойСправочник КАК Т4
            ПО Т3.Ссылка = Т4.Родитель
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.МойСправочник КАК Т5
            ПО Т4.Ссылка = Т5.Родитель
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.МойСправочник КАК Т6
            ПО Т5.Ссылка = Т6.Родитель
СГРУППИРОВАТЬ ПО
    Т1.Ссылка

Как это работает:

  1. Т1 — это наша основная таблица, из которой мы выбираем все элементы.
  2. Т2 — это элементы, у которых родителем является элемент из Т1 (прямые потомки).
  3. Т3 — это элементы, у которых родителем является элемент из Т2 ("внуки").
  4. И так далее для каждого уровня вложенности.
  5. Функция КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...) подсчитывает уникальные элементы на каждом уровне, а затем мы их суммируем, получая итоговое число всех вложенных элементов.

Преимущества:

Недостатки:

Решение 2: Хранение количества в реквизите справочника

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

Логику подсчета нужно реализовать в модуле объекта справочника.

Разберем по шагам:

  1. Добавляем реквизит. В справочнике МойСправочник создаем реквизит КоличествоВложенных.
  2. Реализуем логику обновления. В модуле объекта справочника необходимо подписаться на события ПриЗаписи и ПередУдалением.

    При записи нового элемента, изменении существующего (особенно при смене родителя) или удалении, необходимо запустить процедуру пересчета количества для всей родительской цепочки.

  3. Алгоритм пересчета. При изменении элемента нужно:
    • Для старого родителя (если он был) и всех его "предков" вверх по иерархии уменьшить значение счетчика.
    • Для нового родителя и всех его "предков" вверх по иерархии увеличить значение счетчика.

    Это самая сложная часть, так как требует аккуратной рекурсивной или циклической обработки всех вышестоящих элементов.

  4. Запрос для динамического списка. После реализации этой логики запрос для формы становится предельно простым:
    
    ВЫБРАТЬ
        МойСправочник.Ссылка,
        МойСправочник.Наименование,
        МойСправочник.Родитель,
        МойСправочник.КоличествоВложенных
    ИЗ
        Справочник.МойСправочник КАК МойСправочник
    

Преимущества:

Недостатки:

Решение 3: Серверные вычисления при получении данных

Этот метод представляет собой компромисс между первыми двумя. Мы не меняем структуру данных и не пишем сложный запрос в динамическом списке. Вместо этого, мы переносим логику вычислений на сервер и выполняем ее в момент отображения данных на форме.

Для этого используется событие формы динамического списка ПриПолученииДанныхНаСервере.

Разберем по шагам:

  1. Подготовка запроса. В основном запросе динамического списка мы добавляем "пустое" поле для будущего значения.
    
    ВЫБРАТЬ
        МойСправочник.Ссылка,
        МойСправочник.Наименование,
        МойСправочник.Родитель,
        0 КАК КоличествоВложенных
    ИЗ
        Справочник.МойСправочник КАК МойСправочник
    
  2. Создаем обработчик события. В свойствах формы списка находим событие ПриПолученииДанныхНаСервере и создаем для него обработчик.
  3. Реализуем вычисление и кеширование. Выполнять отдельный запрос на подсчет для каждой строки, которая появляется на экране, — очень плохая идея с точки зрения производительности. Поэтому здесь ключевую роль играет кеширование.

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

    При последующих вызовах ПриПолученииДанныхНаСервере мы уже не обращаемся к базе данных, а быстро берем подсчитанное значение из нашего кеша.

  4. Заполнение данных. В обработчике события мы пробегаемся по строкам полученных данных и для каждой строки подставляем значение из кеша.
    
    &НаСервере
    Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
        
        // Переменная "КешКоличества" - реквизит формы типа Соответствие.
        // Если кеш еще не заполнен, заполняем его.
        Если КешКоличества = Неопределено Тогда
            ЗаполнитьКешКоличестваПодчиненных(); // Процедура, которая один раз все считает и кладет в кеш
        КонецЕсли;
    
        Для Каждого Строка ИЗ Строки Цикл
            НайденноеЗначение = КешКоличества.Получить(Строка.Данные.Ссылка);
            Если НайденноеЗначение <> Неопределено Тогда
                Строка.Ячейки.КоличествоВложенных.УстановитьТекст(НайденноеЗначение);
            КонецЕсли;
        КонецЦикла;
    
    КонецПроцедуры
    

Преимущества:

Недостатки:

Итог

Выбор оптимального метода зависит от конкретной задачи и условий:

← На главную