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

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

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

В рамках данной статьи мы подробно разберем, как решить эту задачу с помощью запросов 1С. Мы рассмотрим различные сценарии, начиная от простой проверки одного элемента и заканчивая пакетной обработкой данных документов, а также проанализируем вопросы производительности и оптимизации СУБД.

Понимание типов иерархии в 1С

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

  1. Иерархия групп и элементов. При таком типе структура состоит из папок (групп) и конечных элементов. Элементы физически не могут иметь подчиненных элементов. В этом случае задача решается максимально просто. Для проверки того, является ли объект конечным, достаточно использовать стандартный реквизит ЭтоГруппа. Если ЭтоГруппа = ЛОЖЬ, то данный элемент гарантированно никому не родитель.
  2. Иерархия элементов. Этот тип используется в справочниках типа «Подразделения» или «Подразделения организаций» в ЗУП. Здесь нет папок, и любой элемент справочника может быть подчинен другому элементу. Реквизит ЭтоГруппа в таких справочниках отсутствует, и для решения нашей задачи нам придется анализировать связь элементов через реквизит Родитель.

Ниже мы рассмотрим решения именно для второго, более сложного случая — иерархии элементов.

Решение 1. Базовый запрос с использованием левого соединения (для всего справочника)

Рассмотрим ситуацию, когда нам необходимо получить список всех подразделений и для каждого из них определить, является ли оно родителем для какого-либо другого подразделения. Для этого мы применим классический прием — ЛЕВОЕ СОЕДИНЕНИЕ таблицы справочника с самой собой.

Проанализируем следующий пример запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Родители.Ссылка КАК Подразделение,
    ВЫБОР
        КОГДА Подчиненные.Ссылка ЕСТЬ NULL
            ТОГДА ЛОЖЬ
        ИНАЧЕ ИСТИНА
    КОНЕЦ КАК ЯвляетсяРодителем
ИЗ
    Справочник.ПодразделенияОрганизаций КАК Родители
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ПодразделенияОрганизаций КАК Подчиненные
        ПО Родители.Ссылка = Подчиненные.Родитель

Разберем по шагам, как работает этот запрос:

  1. В качестве левой таблицы (Родители) мы берем весь список подразделений.
  2. В качестве правой таблицы (Подчиненные) мы берем этот же справочник подразделений.
  3. Мы связываем эти таблицы по условию: ссылка из левой таблицы должна быть записана в реквизит Родитель правой таблицы.
  4. Если у подразделения из левой таблицы есть подчиненные элементы, то связь установится, и поле Подчиненные.Ссылка будет заполнено конкретным значением.
  5. Если у подразделения нет подчиненных, то связь вернет NULL. С помощью конструкции ВЫБОР КОГДА ... ЕСТЬ NULL мы преобразуем этот результат в логическое значение ИСТИНА или ЛОЖЬ.

Решение 2. Выборка конечных элементов («листьев») с родителем

Теперь рассмотрим более специфическую задачу, с которой столкнулся автор темы на форуме: нам нужно выбрать только те подразделения, которые имеют родителя (то есть не являются корневыми элементами первого уровня), но при этом сами не являются родителями ни для кого другого.

Для решения этой задачи мы наложим дополнительные условия в секцию ГДЕ:


ВЫБРАТЬ
    Подразделения.Ссылка КАК Подразделение,
    Подразделения.Родитель КАК Родитель
ИЗ
    Справочник.ПодразделенияОрганизаций КАК Подразделения
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ПодразделенияОрганизаций КАК Подчиненные
        ПО Подразделения.Ссылка = Подчиненные.Родитель
ГДЕ
    // Условие 1: у самого подразделения должен быть родитель (исключаем верхний уровень)
    Подразделения.Родитель <> ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка)
    
    // Условие 2: у подразделения не должно быть подчиненных элементов
    И Подчиненные.Ссылка ЕСТЬ NULL

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

Решение 3. Использование временных таблиц для оптимизации (для работы в документах)

Если мы разрабатываем проверку для проведения документа или заполнения табличной части (например, табличной части документа «Прием на работу»), выполнять полное левое соединение всего справочника подразделений «сам с собой» крайне неэффективно с точки зрения производительности СУБД. На больших базах данных это может привести к заметным задержкам.

Правильным решением будет предварительный отбор только тех подразделений, которые фигурируют в конкретном документе, во временную таблицу (ВТ). Рассмотрим этот оптимизированный вариант:


// ШАГ 1: Выбираем только уникальные подразделения из документа во временную таблицу
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Работники.ПодразделениеОрганизации КАК Подразделение
ПОМЕСТИТЬ ВТ_ПодразделенияДокумента
ИЗ
    Документ.ПриемНаРаботу.Работники КАК Работники
ГДЕ
    Работники.Ссылка = &СсылкаНаДокумент
;

////////////////////////////////////////////////////////////////////////////////
// ШАГ 2: Соединяем временную таблицу со справочником для поиска подчиненных
ВЫБРАТЬ
    ВТ.Подразделение КАК Подразделение,
    ВТ.Подразделение.Родитель КАК Родитель,
    ВЫБОР
        КОГДА Подчиненные.Ссылка ЕСТЬ NULL
            ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК ЭтоКонечныйЭлемент
ИЗ
    ВТ_ПодразделенияДокумента КАК ВТ
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ПодразделенияОрганизаций КАК Подчиненные
        ПО ВТ.Подразделение = Подчиненные.Родитель

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

Решение 4. Быстрая проверка одного конкретного элемента

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

Для точечной проверки лучше всего использовать простой запрос с ограничением ПЕРВЫЕ 1. Это самый легкий и быстрый запрос из всех возможных:


ВЫБРАТЬ ПЕРВЫЕ 1
    Подразделения.Ссылка КАК Ссылка
ИЗ
    Справочник.ПодразделенияОрганизаций КАК Подразделения
ГДЕ
    Подразделения.Родитель = &ПроверяемоеПодразделение

После выполнения этого запроса мы проверяем результат в коде на встроенном языке:


Запрос = Новый Запрос;
Запрос.Текст = "... (текст запроса выше)";
Запрос.УстановитьПараметр("ПроверяемоеПодразделение", ВыбранноеПодразделение);

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

Если РезультатЗапроса.Пустой() Тогда
    // Подчиненных элементов нет. Это подразделение является конечным!
    Сообщить("Выбор корректен.");
Иначе
    // Найдено хотя бы одно подчиненное подразделение
    ВызватьИсключение "Нельзя выбрать это подразделение, так как оно имеет подчиненные отделы!";
КонецЕсли;

Благодаря инструкции ПЕРВЫЕ 1, СУБД прекратит поиск сразу же, как только найдет хотя бы одного «ребенка» для проверяемого элемента. Это обеспечивает максимальное быстродействие.

Важный нюанс: Учет помеченных на удаление элементов

В реальной практике бизнеса часто случается ситуация, когда у подразделения были подчиненные отделы, но со временем их расформировали и пометили на удаление. С точки зрения логики пользователей, такое подразделение фактически стало конечным (рабочим «листом»), так как активных подчиненных у него больше нет.

Если мы не учтем этот момент, наши запросы будут считать такое подразделение «родителем» из-за наличия неудаленных связей с архивными элементами. Чтобы избежать этой ошибки, нам обязательно нужно добавить условие пометки на удаление в связь ЛЕВОГО СОЕДИНЕНИЯ:


ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ПодразделенияОрганизаций КАК Подчиненные
    ПО Родители.Ссылка = Подчиненные.Родитель
    И НЕ Подчиненные.ПометкаУдаления

Добавление условия И НЕ Подчиненные.ПометкаУдаления непосредственно в условие связи (а не в секцию ГДЕ) позволит корректно отсечь удаленные элементы. Если у подразделения остались только помеченные на удаление «дети», связь вернет NULL, и система правильно классифицирует элемент как конечную рабочую единицу.

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

Мы подробно разобрали, как в запросах 1С определять иерархический статус элементов. Подводя итог, дадим несколько рекомендаций:

← На главную