Как настроить условие RLS для вывода всех записей, если пользователя нет в регистре ограничений?

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

При настройке механизма ограничения доступа на уровне записей (RLS) часто возникает типовая бизнес-задача: если для текущего пользователя в регистре прав нет индивидуальных настроек ограничений, ему должен быть открыт полный доступ ко всем элементам справочника. Однако при попытке реализовать такую логику через подзапрос или конструкцию НЕ 1 В (ВЫБРАТЬ ...) разработчики регулярно сталкиваются с тем, что условие отсекает все записи либо вовсе не отрабатывает.

Разберем, почему исходный запрос ведет себя неожиданным образом, и подробно рассмотрим правильные варианты реализации условий RLS.

Анализ типовой ошибки в запросе RLS

Посмотрим на исходный вариант запроса, вызывающий сбой:


Номенклатура ИЗ Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS
    ПО Номенклатура.Подразделение = RLS.Подразделение
ГДЕ (RLS.Пользователь = &ТекущийПользователь
     ИЛИ НЕ 1 В
         (ВЫБРАТЬ
              1 КАК ПолеОтбора
          ИЗ
              РегистрСведений.RLS КАК RLS
          ГДЕ
              RLS.Пользователь = &ТекущийПользователь))

В этой конструкции кроются сразу две фундаментальные проблемы механизма запросов платформы 1С и СУБД:

  1. Превращение левого соединения во внутреннее. Обращение к реквизиту присоединенной таблицы RLS.Пользователь в секции ГДЕ при невыполнении условия отсекает строки, где значение равно NULL. Хотя левое соединение сохраняет все записи основной таблицы, последующее применение фильтра по полям правой таблицы часто приводит к непредвиденному поведению оптимизатора СУБД.
  2. Особенности трехзначной логики SQL и оператора «В». Конструкция вида НЕ <Значение> В (ВЫБРАТЬ ...) при трансляции в SQL трансформируется в предикат NOT IN. Если подзапрос в результате вернет хотя бы одну строку со значением NULL, результатом всего выражения станет UNKNOWN (Неопределено). В логике секции ГДЕ условие считается истинным только тогда, когда результат строго равен TRUE, поэтому вся ветвь проверки отсекается. Использование фиктивных литералов вроде 1 В (ВЫБРАТЬ 1) также часто транслируется СУБД неэффективно.

Вариант 1. Использование двойного левого соединения

Рассмотрим рабочее решение, построенное на двух левых соединениях с регистром ограничений. Первое соединение сопоставляет номенклатуру с подразделением пользователя, а второе — проверяет сам факт наличия пользователя в регистре.

Обратите внимание: отбор по текущему пользователю переносится непосредственно в секцию соединения ПО, что предотвращает разрушение логики левого соединения:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS_ПоПодразделению
        ПО Номенклатура.Подразделение = RLS_ПоПодразделению.Подразделение
        И RLS_ПоПодразделению.Пользователь = &ТекущийПользователь
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.RLS КАК RLS_ВсеЗаписи
        ПО RLS_ВсеЗаписи.Пользователь = &ТекущийПользователь
ГДЕ
    НЕ RLS_ПоПодразделению.Пользователь ЕСТЬ НЕ NULL
    ИЛИ RLS_ВсеЗаписи.Пользователь ЕСТЬ NULL

Проанализируем логику условий в секции ГДЕ:

  1. Если для пользователя вообще нет записей в регистре, таблица RLS_ВсеЗаписи для всех строк номенклатуры вернет NULL. Условие RLS_ВсеЗаписи.Пользователь ЕСТЬ NULL окажется истинным, и пользователь увидит весь справочник.
  2. Если пользователь внесен в регистр хотя бы один раз, поле RLS_ВсеЗаписи.Пользователь уже не будет равно NULL, и доступ начнет регулироваться исключительно первой веткой: НЕ RLS_ПоПодразделению.Пользователь ЕСТЬ NULL, пропуская только те элементы, подразделения которых явно сопоставлены пользователю.

Вариант 2. Использование оператора «НЕ СУЩЕСТВУЕТ»

Если требуется избежать повторного соединения таблиц, воспользуемся оператором проверки существования записей НЕ СУЩЕСТВУЕТ. Данная конструкция устойчива к значениям NULL и корректно транслируется в SQL-оператор NOT EXISTS:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Подразделение В
        (ВЫБРАТЬ
             RLS.Подразделение
         ИЗ
             РегистрСведений.RLS КАК RLS
         ГДЕ
             RLS.Пользователь = &ТекущийПользователь)
    ИЛИ НЕ СУЩЕСТВУЕТ
        (ВЫБРАТЬ
             1
         ИЗ
             РегистрСведений.RLS КАК RLS
         ГДЕ
             RLS.Пользователь = &ТекущийПользователь)

В этом случае проверка делится на два четких сценария: прямое вхождение подразделения в список разрешенных либо полное отсутствие каких-либо строк по текущему пользователю в регистре прав.

Вариант 3. Оптимизация через параметры сеанса (стандарт разработки)

Важно помнить, что логический оператор ИЛИ на верхнем уровне условий RLS является критическим фактором снижения производительности. При чтении списка номенклатуры платформа встраивает это условие в каждый запрос, что заставляет СУБД сканировать таблицы целиком (операции Index Scan или Table Scan) и многократно вычислять подзапросы.

Для высоконагруженных систем правильнее перенести предварительные проверки в модуль сеанса:

  1. При старте сеанса проверяем, заданы ли настройки для текущего пользователя в регистре РегистрСведений.RLS.
  2. Заполняем параметры сеанса: например, булевый флаг ОграничиватьДоступПоПодразделениям и массив разрешенных подразделений РазрешенныеПодразделения.
  3. В самом тексте RLS пишем прямое и быстрое условие:

ГДЕ
    &ОграничиватьДоступПоПодразделениям = ЛОЖЬ
    ИЛИ Номенклатура.Подразделение В (&РазрешенныеПодразделения)

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

← На главную