У меня есть база данных с несколькими отношениями «многие ко многим», и я пытаюсь написать оператор 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". Я подозреваю, что даже если бы я преодолел эту ошибку, я все равно не смог бы вернуть желаемый набор результатов. Есть идеи?
Как следует из сообщения об ошибке, слева от вашего последнего соединения нет p_role_id, но условием соединения является USING (p_role_id). Если честно, мне кажется, что p_role_id нигде нет.
@ValNik - вы правы - я забыл упомянуть таблицу role_permissions, которая, как вы и подозревали, содержит столбцы role_id и Permission_id. Я обновил вопрос, включив в него это. Спасибо!





Вопрос мне не на 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;
Это соответствует моему «лучшему» сценарию, намного проще и понятнее. Спасибо!!!
Предлагаю немного другой порядок соединения и группировки таблиц.
Сначала мы собираем все разрешения для пользователя (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
Вы говорите
a role can have multiple permissions. У вас есть таблицаrole_permission(role_id,permission_id)? Эта таблица используется в запросе.