SQL-запрос выбора ролей/разрешений пользователя

У меня есть база данных с несколькими отношениями «многие ко многим», и я пытаюсь написать оператор SELECT, который извлекает набор результатов с ОДНОЙ СТРОКОЙ НА КАЖДОГО ПОЛЬЗОВАТЕЛЯ. Для этого я хотел бы объединить разрешения в один столбец.

В конструкции базы данных приложения пользователь может иметь несколько разрешений, роль может иметь несколько разрешений, а пользователь также может иметь несколько ролей, тем самым усугубляя разрешения пользователя.

Вот соответствующие таблицы:

// Main tables
user:
- id
- username
- email

role:
- id
- name
- display_name

permission:
- id
- name
- display_name

// Many-to-many connector tables
user_role:
- user_id
- role_id

user_permission:
- user_id
- permission_id

role_permission:
- role_id
- permission_id

(НЕ ХОРОШО) Я создал запрос, который успешно объединяет разрешения пользователей, поскольку это всего лишь один агрегат. Однако я не могу понять, как получить двумерный агрегат при добавлении разрешений ролей.

(ЛУЧШЕ) Из следующего запроса я хотел бы иметь по крайней мере шестой столбец с именем «roles_permissions_array», который в псевдокоде представлен следующим образом: «Для каждой роли, найденной в столбце «user_role_array», выберите массив всех разрешений ."

(ЛУЧШИЙ) Чтобы пойти дальше, было бы неплохо также иметь версию запроса, которая объединяет ВСЕ разрешения (как разрешения прямого пользователя, так и разрешения ролей) в один столбец (дедуплицированный), чтобы мы могли видеть единый источник всех разрешений для каждого пользователя. В этом сценарии выходные столбцы будут следующими: "user_id", "user_name", "user_email", "user_role_array" и "user_permissions_array". Последним из них будет консолидированный массив всех разрешений пользователя И всех разрешений ролей.

Вот начало моей попытки:

SELECT DISTINCT 
    -- "user" columns
    u.id as "user_id", u.user_name as "user_user_name", u.email as "user_email",
    p_agg.user_permissions_array,
    r_agg.user_role_array
    FROM users."user" u
-- REGULAR JOINS
LEFT JOIN users.user_role ur on ur.user_id = u.id
LEFT JOIN users.role_permission rp on ur.role_id = rp.role_id
-- START AGGREGATES
LEFT JOIN (
    SELECT array_agg(r.name) as user_role_array, ur.user_id
    FROM users.user_role ur
    JOIN users.role r on r.id = ur.role_id
    GROUP BY ur.user_id
) r_agg using(user_id)
LEFT JOIN (
    SELECT array_agg(p.name) as user_permissions_array, up.user_id
    FROM users.user_permission up
    JOIN users.permission p on up.permission_id = p.id
    GROUP BY up.user_id
) p_agg using(user_id)
-- rp_agg_parent
-- NOT WORKING FROM HERE DOWN
LEFT JOIN (
    SELECT array_agg(role_permission_array) as two_dimensional_role_permissions_array
    FROM (
        SELECT p.role_id, r.name as "role_name", array_agg(p.permission_name) as "role_permission_array"
        FROM (
            SELECT r.id as "role_id", r.name as "role_name", p.name as "permission_name"
            FROM users.role_permission rp
            JOIN users.role r on r.id = rp.role_id
            JOIN users.permission p on p.id = rp.permission_id
            GROUP BY p.name, r.name, r.id
            ORDER BY p.name
        ) as p
        JOIN users.user_role ur on ur.role_id = p.role_id
        JOIN users.role r on r.id = ur.role_id
        GROUP BY r.name, p.role_id
        order by role_name
    ) AS rp_agg_sub
    JOIN users.role_permission rp on rp.role_id = rp_agg_sub.role_id
    GROUP BY rp_agg_sub.role_name
) rp_agg_parent using(p_role_id)
order by u.user_name;

Но я просто получаю сообщение об ошибке: "...column "p_role_id" specified in USING clause does not exist in left table". Я подозреваю, что даже если бы я преодолел эту ошибку, я все равно не смог бы вернуть желаемый набор результатов. Есть идеи?

Вы говорите a role can have multiple permissions. У вас есть таблица role_permission(role_id,permission_id)? Эта таблица используется в запросе.

ValNik 28.05.2024 00:15

Как следует из сообщения об ошибке, слева от вашего последнего соединения нет p_role_id, но условием соединения является USING (p_role_id). Если честно, мне кажется, что p_role_id нигде нет.

Islingre 28.05.2024 00:49

@ValNik - вы правы - я забыл упомянуть таблицу role_permissions, которая, как вы и подозревали, содержит столбцы role_id и Permission_id. Я обновил вопрос, включив в него это. Спасибо!

wdc92 28.05.2024 13:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вопрос мне не на 100% ясен, но я интерпретирую его так:

  • Есть пользователи, роли и разрешения.
  • Роль — это набор разрешений (роли не обязательно являются непересекающимися наборами).
  • Одному пользователю может быть назначено несколько ролей.
  • Кроме того, пользователю может быть предоставлено несколько разрешений напрямую (не через роль).
  • Вы хотите получить:
    • вся информация о пользователе
    • имена всех назначенных ролей
    • дедуплицированные имена всех разрешений (независимо от того, напрямую или через роль).
  • Все ваши таблицы живут в схеме users.

Попробуй это:

SELECT
    u.id AS user_id,
    u.username AS user_name,
    u.email AS user_email,
    ARRAY_AGG(DISTINCT r.name ORDER BY r.name) AS user_role_array,
    ARRAY_AGG(DISTINCT p.name ORDER BY p.name) AS user_permission_array
FROM users.user u
LEFT JOIN users.user_role ur ON ur.user_id = u.id
LEFT JOIN users.role r ON r.id = ur.role_id
LEFT JOIN users.role_permission rp ON rp.role_id = r.id
LEFT JOIN users.user_permission up ON up.user_id = u.id
LEFT JOIN users.permission p ON p.id IN (rp.permission_id, up.permission_id)
GROUP BY u.id
ORDER BY u.username;

Это соответствует моему «лучшему» сценарию, намного проще и понятнее. Спасибо!!!

wdc92 28.05.2024 13:51

Предлагаю немного другой порядок соединения и группировки таблиц.
Сначала мы собираем все разрешения для пользователя (user_id,permission_id), назначенные непосредственно пользователю или через роль.
Затем объедините имена разрешений и ролей и агрегируйте их в массивы.
Наконец, присоединитесь к таблице пользователей.

См. пример

select user_id,username,email
  ,user_role_array
  ,user_permission_array
from(
  select user_id 
     ,ARRAY_AGG(DISTINCT r.name ORDER BY r.name) FILTER (WHERE perm_type='r')
         AS user_role_array
     ,ARRAY_AGG(DISTINCT p.name ORDER BY p.name) AS user_permission_array
  from( -- all user permissions - direct and thru role
      select up.user_id,permission_id, 0 role_id,'u' perm_type
      from user_permission up
      union all
      select ur.user_id,rp.permission_id,ur.role_id,'r' perm_type
      from user_role ur
      left join role_permission rp on ur.role_id=rp.role_id
   )allP
  left join permission p on p.id=allP.permission_id
  left join role r on r.id=allP.role_id
  group by user_id
)uarr
left join "user" u on u.id=uarr.user_id

Демо

Другие вопросы по теме