私はいくつかの選択をしなければなりませんでした。ユーザー権限とロール権限の両方がある場合は、ユーザー権限が適用されます。ユーザーとロールを zuser と zrole に置き換えました。これらは postgres の予約語であり、引用するのが好きではないためです。クエリは、現在の形式ではあまり美的ではありませんが、機能しているようです。データは架空のものです。
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path='tmp';
CREATE TABLE permission
( id SERIAL PRIMARY KEY
, permission_key VARCHAR(32) NOT NULL UNIQUE
, permission_name VARCHAR(32) NOT NULL
);
INSERT INTO permission(id,permission_key, permission_name) VALUES
(1, 'Eat', 'Eat' ) , (2, 'Drink', 'Drink' )
,(3, 'Shit', 'Shit' ) , (4, 'Urinate', 'Urinate' )
;
CREATE TABLE zrole
( id SERIAL PRIMARY KEY
, role_name varchar(32) NOT NULL
);
INSERT INTO zrole(id, role_name) VALUES
(1, 'Manager'), (2, 'Employee'), (3, 'Client') , (4, 'Visitor')
;
CREATE TABLE zuser
( id SERIAL PRIMARY KEY
, username VARCHAR(32) UNIQUE
);
INSERT INTO zuser(id, username) VALUES
(1, 'Jan Kees de Jager'), (2, 'Wildplasser'), (3, 'Joop') , (4, 'Mina')
;
CREATE TABLE role_permissions
( id SERIAL PRIMARY KEY
, role_id INTEGER NOT NULL REFERENCES zrole(id)
, permission_id INTEGER NOT NULL REFERENCES permission(id)
, created_date DATE NOT NULL
, value BOOLEAN NOT NULL DEFAULT FALSE
, UNIQUE (role_id,permission_id)
);
INSERT INTO role_permissions( id, role_id, permission_id, created_date, value) VALUES
(1,1,1, '2012-01-01', True )
,(2,1,2, '2012-01-01', False )
,(3,2,2, '2012-01-01', True )
,(4,2,3, '2012-01-01', False )
,(5,2,4, '2012-01-01', True )
,(6,3,2, '2012-01-01', True )
,(7,3,3, '2012-01-01', True )
,(8,3,4, '2012-01-01', True )
,(9,4,2, '2012-01-01', True )
,(10,4,3, '2012-01-01', True )
,(11,4,4, '2012-01-01', True )
;
CREATE TABLE user_permissions
( id SERIAL PRIMARY KEY
, user_id INTEGER NOT NULL REFERENCES zuser(id)
, permission_id INTEGER NOT NULL REFERENCES permission(id)
, created_date DATE NOT NULL
, value BOOLEAN NOT NULL DEFAULT FALSE
, UNIQUE (user_id,permission_id)
);
INSERT INTO user_permissions( id, user_id, permission_id, created_date, value) VALUES
(1,1,1, '2012-01-01', False )
,(2,1,2, '2012-01-01', False )
,(3,2,2, '2012-01-01', True )
,(4,3,2, '2012-01-01', True )
,(5,4,1, '2012-01-01', True )
;
CREATE TABLE user_roles
( id SERIAL PRIMARY KEY
, user_id INTEGER NOT NULL REFERENCES zuser(id)
, role_id INTEGER NOT NULL REFERENCES zrole(id)
, created_date DATE NOT NULL
, UNIQUE (user_id, role_id)
);
INSERT INTO user_roles (id, user_id, role_id, created_date) VALUES
(1,1,1, '2010-01-01' )
,(2,2,2, '2010-01-01' )
,(3,3,4, '2010-01-01' )
,(4,4,3, '2010-01-01' )
-- uncomment the next line to add a duplicate role
-- ,(5,2,4, '2010-01-01' )
;
WITH lutser AS (
SELECT up.user_id AS user_id
, up.permission_id AS permission_id
, up.value AS uval
FROM user_permissions up
)
, roler AS (
SELECT
ur.user_id AS user_id
, rp.permission_id AS permission_id
, rp.value AS rval
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
)
SELECT us.username
, pe.permission_name
, pe.id AS permission_id
, lu.uval AS uval
, ro.rval AS rval
, COALESCE(lu.uval , ro.rval) AS tval
FROM lutser lu
FULL JOIN roler ro ON ro.user_id = lu.user_id
AND ro.permission_id = lu.permission_id
JOIN zuser us ON us.id = COALESCE(lu.user_id ,ro.user_id)
JOIN permission pe ON pe.id = COALESCE(ro.permission_id , lu.permission_id)
;
結果:
username | permission_name | permission_id | uval | rval | tval
-------------------+-----------------+---------------+------+------+------
Jan Kees de Jager | Eat | 1 | f | t | f
Jan Kees de Jager | Drink | 2 | f | f | f
Wildplasser | Drink | 2 | t | t | t
Wildplasser | Shit | 3 | | f | f
Wildplasser | Urinate | 4 | | t | t
Joop | Drink | 2 | t | t | t
Joop | Shit | 3 | | t | t
Joop | Urinate | 4 | | t | t
Mina | Eat | 1 | t | | t
Mina | Drink | 2 | | t | t
Mina | Shit | 3 | | t | t
Mina | Urinate | 4 | | t | t
(12 rows)
ところで: 上記のクエリはまだ正しくありません。ユーザーが複数のロールに属している場合、クエリはそのユーザーに対して複数の行を生成します。ロール サブクエリには、distinct/max() を追加する必要があります。
更新: 1 人あたりの役割の重複の問題を解決するために、この二重にネストされた CTE を作成しました。
WITH lutser AS (
WITH aggr AS (
WITH rope AS (
SELECT DISTINCT
ur.user_id AS user_id
, rp.permission_id AS permission_id
, rp.value AS value
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
GROUP BY ur.user_id , rp.permission_id , rp.value
)
SELECT user_id,permission_id, value
FROM rope yes
WHERE yes.value = True
UNION ALL
SELECT user_id,permission_id, value
FROM rope nono
WHERE nono.value = False
AND NOT EXISTS (SELECT * FROM rope nx
WHERE nx.user_id= nono.user_id
AND nx.permission_id= nono.permission_id
AND nx.value = True
)
)
SELECT COALESCE(up.user_id , ag.user_id) AS user_id
, COALESCE(up.permission_id , ag.permission_id) AS permission_id
, up.value AS uval
, ag.value AS rval
FROM user_permissions up
FULL JOIN aggr ag ON ag.user_id = up.user_id AND ag.permission_id = up.permission_id
)
SELECT us.username
, pe.permission_name
, lu.uval AS uval
, lu.rval AS rval
, COALESCE(lu.uval , lu.rval) AS tval
FROM lutser lu
JOIN zuser us ON us.id = lu.user_id
JOIN permission pe ON pe.id = lu.permission_id
;
その機能は、データ行,(5,2,4, '2010-01-01' )
を user_role テーブルに追加/コメント解除することで表示できます。ここでも、選択を迫られました。1 人のユーザーに 2 つの役割が存在し、真理値が競合する場合は、どちらかが優先されますTrue
。クエリは簡素化/美化できると思いますが、少なくとも現在は正しく機能しています。