При настройке механизма ограничения доступа на уровне записей (RLS) часто возникает типовая бизнес-задача: если для текущего пользователя в регистре прав нет индивидуальных настроек ограничений, ему должен быть открыт полный доступ ко всем элементам справочника. Однако при попытке реализовать такую логику через подзапрос или конструкцию НЕ 1 В (ВЫБРАТЬ ...) разработчики регулярно сталкиваются с тем, что условие отсекает все записи либо вовсе не отрабатывает.
Разберем, почему исходный запрос ведет себя неожиданным образом, и подробно рассмотрим правильные варианты реализации условий RLS.
Посмотрим на исходный вариант запроса, вызывающий сбой:
Номенклатура ИЗ Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS
ПО Номенклатура.Подразделение = RLS.Подразделение
ГДЕ (RLS.Пользователь = &ТекущийПользователь
ИЛИ НЕ 1 В
(ВЫБРАТЬ
1 КАК ПолеОтбора
ИЗ
РегистрСведений.RLS КАК RLS
ГДЕ
RLS.Пользователь = &ТекущийПользователь))
В этой конструкции кроются сразу две фундаментальные проблемы механизма запросов платформы 1С и СУБД:
RLS.Пользователь в секции ГДЕ при невыполнении условия отсекает строки, где значение равно NULL. Хотя левое соединение сохраняет все записи основной таблицы, последующее применение фильтра по полям правой таблицы часто приводит к непредвиденному поведению оптимизатора СУБД.НЕ <Значение> В (ВЫБРАТЬ ...) при трансляции в SQL трансформируется в предикат NOT IN. Если подзапрос в результате вернет хотя бы одну строку со значением NULL, результатом всего выражения станет UNKNOWN (Неопределено). В логике секции ГДЕ условие считается истинным только тогда, когда результат строго равен TRUE, поэтому вся ветвь проверки отсекается. Использование фиктивных литералов вроде 1 В (ВЫБРАТЬ 1) также часто транслируется СУБД неэффективно.Рассмотрим рабочее решение, построенное на двух левых соединениях с регистром ограничений. Первое соединение сопоставляет номенклатуру с подразделением пользователя, а второе — проверяет сам факт наличия пользователя в регистре.
Обратите внимание: отбор по текущему пользователю переносится непосредственно в секцию соединения ПО, что предотвращает разрушение логики левого соединения:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS_ПоПодразделению
ПО Номенклатура.Подразделение = RLS_ПоПодразделению.Подразделение
И RLS_ПоПодразделению.Пользователь = &ТекущийПользователь
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS_ВсеЗаписи
ПО RLS_ВсеЗаписи.Пользователь = &ТекущийПользователь
ГДЕ
НЕ RLS_ПоПодразделению.Пользователь ЕСТЬ НЕ NULL
ИЛИ RLS_ВсеЗаписи.Пользователь ЕСТЬ NULL
Проанализируем логику условий в секции ГДЕ:
RLS_ВсеЗаписи для всех строк номенклатуры вернет NULL. Условие RLS_ВсеЗаписи.Пользователь ЕСТЬ NULL окажется истинным, и пользователь увидит весь справочник.RLS_ВсеЗаписи.Пользователь уже не будет равно NULL, и доступ начнет регулироваться исключительно первой веткой: НЕ RLS_ПоПодразделению.Пользователь ЕСТЬ NULL, пропуская только те элементы, подразделения которых явно сопоставлены пользователю.Если требуется избежать повторного соединения таблиц, воспользуемся оператором проверки существования записей НЕ СУЩЕСТВУЕТ. Данная конструкция устойчива к значениям NULL и корректно транслируется в SQL-оператор NOT EXISTS:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Подразделение В
(ВЫБРАТЬ
RLS.Подразделение
ИЗ
РегистрСведений.RLS КАК RLS
ГДЕ
RLS.Пользователь = &ТекущийПользователь)
ИЛИ НЕ СУЩЕСТВУЕТ
(ВЫБРАТЬ
1
ИЗ
РегистрСведений.RLS КАК RLS
ГДЕ
RLS.Пользователь = &ТекущийПользователь)
В этом случае проверка делится на два четких сценария: прямое вхождение подразделения в список разрешенных либо полное отсутствие каких-либо строк по текущему пользователю в регистре прав.
Важно помнить, что логический оператор ИЛИ на верхнем уровне условий RLS является критическим фактором снижения производительности. При чтении списка номенклатуры платформа встраивает это условие в каждый запрос, что заставляет СУБД сканировать таблицы целиком (операции Index Scan или Table Scan) и многократно вычислять подзапросы.
Для высоконагруженных систем правильнее перенести предварительные проверки в модуль сеанса:
РегистрСведений.RLS.ОграничиватьДоступПоПодразделениям и массив разрешенных подразделений РазрешенныеПодразделения.
ГДЕ
&ОграничиватьДоступПоПодразделениям = ЛОЖЬ
ИЛИ Номенклатура.Подразделение В (&РазрешенныеПодразделения)
Такой подход полностью исключает постоянные обращения к регистру сведений при чтении номенклатуры и гарантирует максимальную производительность пользовательского интерфейса.