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