gifts2017

Пример получения в запросе всех подразделений с учётом иерархии (неограниченный уровень вложенности подразделений)

Опубликовал megatrend - (megatrend) в раздел Программирование - Практика программирования

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

Постановка задачи : имеется регистр сведений опЗапретПремированияПодразделений, в который введены подразделения, запрещённые к премированию. В этом регистре есть измерения Организация и ПодразделениеОрганизации, а также ресурсы ПремированиеРазрешено и СУчетомИерархии.

Требуется : получить все запрещённые к премированию подразделения.

Решение задачи :

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

2) Посчитать количество полученных подразделений. N = 1.

3) Соединить полученную таблицу подразделений ЗапрещенныеПодразделенияN со всей таблицей подразделений ПодразделенияОрганизаций по условию ЗапрещенныеПодразделения.ПодразделениеОрганизации = ПодразделенияОрганизаций.Родитель, тем самым получить всех потомков первого уровня всех найденных ранее подразделений.

4) Объединить результат п.3 с ранее полученной таблицей, исключив совпадения. Результат объединения поместить во временную таблицу ЗапрещенныеПодразделения(N+1) .

5) Посчитать количество полученных в п.4 подразделений.
Если оно совпадает с количеством подразделений, полученных на предыдущем шаге (т.е. в таблице ЗапрещенныеПодразделенияN), то завершить работу, выдав в качестве ответа колонку ПодразделениеОрганизации последней (или предпоследней!) временной таблицы.
В противном случае увеличить N на единицу и перейти к п.3.

// получает все запрещенные к премированию подразделения с учетом иерархии
Функция ПолучитьВсеЗапрещенныеПодразделения(Организация, ДатаОтчета)

    МВТ = Новый МенеджерВременныхТаблиц;

   
Запрос = Новый Запрос();
   
Запрос.МенеджерВременныхТаблиц = МВТ;

   
// 1) сначала получим сырые данные

   
Запрос.Текст = "ВЫБРАТЬ
    |   опЗапретПремированияПодразделенийСрезПоследних.СУчетомИерархии,
    |   опЗапретПремированияПодразделенийСрезПоследних.ПодразделениеОрганизации
    |ПОМЕСТИТЬ ЗапрещенныеПодразделения1
    |ИЗ
    |   РегистрСведений.опЗапретПремированияПодразделений.СрезПоследних(&ДатаСреза, Организация = &Организация) КАК опЗапретПремированияПодразделенийСрезПоследних
    |ГДЕ
    |   НЕ опЗапретПремированияПодразделенийСрезПоследних.ПремированиеРазрешено"
;

   
Запрос.УстановитьПараметр("Организация", Организация);
   
Запрос.УстановитьПараметр("ДатаСреза", КонецДня(ДатаОтчета));

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

   
Выборка.Следующий();

   
// 2) получим количество записей временной таблицы
   
КолВоЗаписей1 = Выборка.Количество;

   
КолВоЗаписей2 = 0;

   
НомерЦикла1 = 1;

   
// 3) затем напишем циклический запрос, в котором будут меняться номера вложенных таблиц

    // 4) всё время результат будем объединять с исходной таблицей (без совпадений!)

   
Запрос.Текст = "ВЫБРАТЬ
    |   ЗапрещенныеПодразделения.СУчетомИерархии,
    |   ПодразделенияОрганизаций.Ссылка КАК ПодразделениеОрганизации
    |ПОМЕСТИТЬ ЗапрещенныеПодразделения1
    |ИЗ
    |   ЗапрещенныеПодразделения0 КАК ЗапрещенныеПодразделения
    |       ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПодразделенияОрганизаций КАК ПодразделенияОрганизаций
    |       ПО ЗапрещенныеПодразделения.ПодразделениеОрганизации = ПодразделенияОрганизаций.Родитель
    |           И (ЗапрещенныеПодразделения.СУчетомИерархии)
    |
    |ОБЪЕДИНИТЬ
    |
    |ВЫБРАТЬ
    |   ЗапрещенныеПодразделения.СУчетомИерархии,
    |   ЗапрещенныеПодразделения.ПодразделениеОрганизации
    |ИЗ
    |   ЗапрещенныеПодразделения0 КАК ЗапрещенныеПодразделения"
;

   
// условие выхода из цикла - количество искомых подразделений перестанет расти

   
Пока КолВоЗаписей1 <> КолВоЗаписей2 Цикл

       
КолВоЗаписей2 = КолВоЗаписей1;

       
// циклически заменяем номера вложенных таблиц

       
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ЗапрещенныеПодразделения" + НомерЦикла1, "ЗапрещенныеПодразделения" + Число(НомерЦикла1 + 1));
       
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ЗапрещенныеПодразделения" + Число(НомерЦикла1 - 1), "ЗапрещенныеПодразделения" + НомерЦикла1);

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

       
Выборка.Следующий();

       
// 5) получим количество записей временной таблицы
       
КолВоЗаписей1 = Выборка.Количество;

       
НомерЦикла1 = НомерЦикла1 + 1;

    КонецЦикла;

   
// последняя (или предпоследняя!) таблица даст нужный результат, в колонке "ПодразделениеОрганизации"

    // последняя таблица имеет номер НомерЦикла1 (он только что был увеличен на 1)
    // предпоследняя таблица имеет номер (НомерЦикла1-1)

   
Запрос.Текст = "ВЫБРАТЬ
    |   ЗапрещенныеПодразделения.СУчетомИерархии,
    |   ЗапрещенныеПодразделения.ПодразделениеОрганизации
    |ИЗ
    |   ЗапрещенныеПодразделения0 КАК ЗапрещенныеПодразделения"
;

   
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ЗапрещенныеПодразделения0", "ЗапрещенныеПодразделения" + НомерЦикла1);
   
Результат = Запрос.Выполнить();

    Возврат
Результат.Выгрузить().ВыгрузитьКолонку("ПодразделениеОрганизации");

КонецФункции
// ПолучитьВсеЗапрещенныеПодразделения()

.

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

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

Этот код будет корректно работать, даже если в справочнике будут некорректные данные (явные ошибки в базе данных), например, когда подразделение-родитель является потомком своего потомка.

См. также

Подписаться Добавить вознаграждение
Комментарии
1. MagIvan (RailMen) 10.09.12 18:51
Жутко неоптимально :evil:
Совет: книги по основам программирования в 1С и оптимизации запросов.
+
минимальные требования для сдачи экзамена 1С:Специалист по платформе.
2. program program (prodines) 20.11.13 12:33
А если так?

Запрос.Текст = "ВЫБРАТЬ
| опЗапретПремированияПодразделенийСрезПоследних.СУчетомИерархии,
| опЗапретПремированияПодразделенийСрезПоследних.ПодразделениеОрганизации
|ИЗ
| РегистрСведений.опЗапретПремированияПодразделений.СрезПоследних(&ДатаСреза, Организация = &Организация) КАК опЗапретПремированияПодразделенийСрезПоследних
|ГДЕ
| НЕ опЗапретПремированияПодразделенийСрезПоследних.ПремированиеРазрешено";

Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("ДатаСреза", КонецДня(ДатаОтчета));

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

МассивПодразделенийВерхнегоУровня = Результат.Выгрузить().ВыгрузитьКолонку("ПодразделениеОрганизации");


Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПодразделенияОрганизаций.Ссылка КАК Подразделение
|ИЗ
| Справочник.ПодразделенияОрганизаций КАК ПодразделенияОрганизаций
|ГДЕ
| ПодразделенияОрганизаций.Ссылка В ИЕРАРХИИ(&МассивПодразделенийВерхнегоУровня)";

Запрос.УстановитьПараметр("МассивПодразделенийВерхнегоУровня",МассивПодразделенийВерхнегоУровня);

Результат = Запрос.Выполнить();
3. megatrend - (megatrend) 20.11.13 12:42
(2) prodines, всё гениальное просто :)
4. Сергей (ildarovich) 21.11.14 13:15
В статье Уровни, глубина, прародители, циклы и аналоги запросом в Примере 3 показан другой (более быстрый и компактный?) метод решения той же задачи. У него есть определенное сходство с методом, приведенным здесь, но там не множество достижимости на каждом шаге наращивается, а в одной таблице накапливаются связи родитель - потомок, причем на каждом этапе охват длины связей вырастает вдвое. За счет этого нужно при 100 уровнях не 100 соединений сделать, а всего 7.