При работе с иерархическими структурами данных в системе 1С:Предприятие, будь то справочники или планы счетов, разработчики часто сталкиваются с задачей получения так называемых «листовых узлов». Листовые узлы — это элементы самого нижнего уровня, которые не имеют подчиненных объектов (детей). В данной статье мы подробно разберем, как решить эту задачу с помощью одного запроса, проанализируем различные подходы и выясним нюансы работы с планами счетов.
Самый эффективный и распространенный способ определить, является ли элемент «листом», — это попробовать найти его в списке родителей этой же таблицы. Если элемент не встречается в поле Родитель ни у одной записи, значит, ниже него по иерархии ничего нет.
Давайте рассмотрим структуру запроса, предложенную в обсуждении. Мы будем использовать ЛЕВОЕ СОЕДИНЕНИЕ таблицы самой с собой. В левой части соединения будет основная таблица, из которой мы хотим получить данные, а в правой — та же самая таблица, но выступающая в роли «проверки на наличие детей».
ВЫБРАТЬ
Хозрасчетный.Ссылка КАК Ссылка
ИЗ
ПланСчетов.Хозрасчетный КАК Хозрасчетный
ЛЕВОЕ СОЕДИНЕНИЕ ПланСчетов.Хозрасчетный КАК Хозрасчетный1
ПО Хозрасчетный.Ссылка = Хозрасчетный1.Родитель
ГДЕ
Хозрасчетный1.Ссылка ЕСТЬ NULL
Проанализируем этот код по шагам:
ПланСчетов.Хозрасчетный. Чтобы система их различала, мы присваиваем им псевдонимы (алиасы). Первая таблица — основной источник, вторая — вспомогательный.Ссылка из основной таблицы с полем Родитель во вспомогательной таблице.Хозрасчетный1.Ссылка будет заполнено. Если же подчиненных элементов нет, соединение вернет пустое значение. Условие ГДЕ Хозрасчетный1.Ссылка ЕСТЬ NULL как раз отсекает все счета, у которых есть «дети», оставляя только нижний уровень.В планах счетов задача осложняется тем, что нам часто нужно учитывать не только структуру иерархии, но и логические признаки ведения учета. Например, счет может быть нижним уровнем в дереве, но при этом являться групповым по своей сути или иметь специфические настройки. Рассмотрим уточнение запроса, которое обсуждалось на форуме.
Если нам необходимо исключить счета верхнего уровня (корневые счета), которые не имеют родителей (например, счет «70»), и оставить только конкретные субсчета (70.01, 70.01.01), мы добавим проверку на пустую ссылку родителя:
ВЫБРАТЬ
ПланСчетовУПП.Ссылка КАК Счет
ИЗ
ПланСчетов.Хозрасчетный КАК ПланСчетовУПП
ЛЕВОЕ СОЕДИНЕНИЕ ПланСчетов.Хозрасчетный КАК ПодчиненныеСчета
ПО ПланСчетовУПП.Ссылка = ПодчиненныеСчета.Родитель
ГДЕ
ПодчиненныеСчета.Ссылка ЕСТЬ NULL
И НЕ ПланСчетовУПП.Родитель = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка)
Важный нюанс: В некоторых конфигурациях (например, УПП) используется реквизит ЗапретитьИспользоватьВПроводках. Однако, как верно подмечено в обсуждении, опираться только на него нельзя. Если у счета есть субсчета, 1С автоматически запрещает проводки по родителю, но наличие этого флага не всегда гарантирует, что перед нами «лист». Использование левого соединения является более надежным структурным методом.
Хотя левое соединение считается более производительным на большинстве СУБД (таких как MS SQL Server), иногда для наглядности кода используют вложенный запрос с оператором НЕ В. Разберем, как это выглядит:
ВЫБРАТЬ
Счета.Ссылка
ИЗ
ПланСчетов.Хозрасчетный КАК Счета
ГДЕ
Счета.Ссылка НЕ В (
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВспСчета.Родитель
ИЗ
ПланСчетов.Хозрасчетный КАК ВспСчета
ГДЕ
ВспСчета.Родитель <> ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка)
)
В этом случае мы выбираем все счета, ссылки которых отсутствуют в списке всех существующих родителей. Этот метод интуитивно понятнее, но стоит помнить, что при работе с огромными справочниками (миллионы записей) такой запрос может выполняться медленнее, чем через соединение.
В отличие от планов счетов, в иерархических справочниках 1С существует стандартный реквизит ЭтоГруппа. Казалось бы, достаточно просто отобрать элементы, где ЭтоГруппа = Ложь. Однако это не всегда решает задачу получения именно нижнего уровня. Почему?
Рассмотрим ситуацию: у нас есть группа «Спецодежда», внутри неё группа «Зимняя», а в ней — элементы (куртки). Если мы просто выберем элементы, мы получим куртки. Но что, если группа «Летняя» пуста? Метод левого соединения позволит нам найти пустые группы, которые фактически являются «тупиковыми» ветками иерархии, если это требуется бизнес-логикой. Если же нам нужны только конечные элементы-данные, мы комбинируем условия:
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Подчиненные
ПО Номенклатура.Ссылка = Подчиненные.Родитель
ГДЕ
Подчиненные.Ссылка ЕСТЬ NULL
И Номенклатура.ЭтоГруппа = ЛОЖЬ
Если иерархический список очень велик (например, справочник номенклатуры в крупном ритейле), выполнение соединения «таблица к таблице» может нагрузить сервер. В таких случаях мы рекомендуем использовать временные таблицы для предварительного сбора всех родителей.
Разберем алгоритм оптимизации:
Родитель.
// Шаг 1: Получаем список всех родителей
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПланСчетов.Родитель КАК Родитель
ПОМЕСТИТЬ ВтРодители
ИЗ
ПланСчетов.Хозрасчетный КАК ПланСчетов
ГДЕ
ПланСчетов.Родитель <> ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка)
ИНДЕКСИРОВАТЬ ПО
Родитель
;
// Шаг 2: Получаем листовые узлы
ВЫБРАТЬ
ПланСчетов.Ссылка
ИЗ
ПланСчетов.Хозрасчетный КАК ПланСчетов
ЛЕВОЕ СОЕДИНЕНИЕ ВтРодители КАК ВтРодители
ПО ПланСчетов.Ссылка = ВтРодители.Родитель
ГДЕ
ВтРодители.Родитель ЕСТЬ NULL
Индексация здесь играет ключевую роль. Поиск по индексированной временной таблице происходит значительно быстрее, так как СУБД не приходится каждый раз сканировать основную таблицу плана счетов для проверки условий соединения.
Для получения нижнего уровня иерархии в 1С наиболее надежным и универсальным инструментом является левое соединение по полю Родитель. При работе с планами счетов важно учитывать контекст задачи: нужны ли вам только те счета, по которым разрешены проводки, или же все «листья» структуры. Использование временных таблиц и индексов поможет сохранить высокую скорость работы системы даже при сложных многоуровневых структурах.