Reputation: 5
I have the following tables: ROLE, PRIVILEGE and ROLE_PRIV. Every role and every privilege has a unique ID and name, while ROLE_PRIV contains the link between the two tables, which indicates the privileges of every role (with every role ID+priv ID combination being unique).
I am trying to generate a report where I will have a column with ALL privileges and the top row listing all the role IDs and then having a YES/NO in the cells below, indicating whether this role has the specific privilege assigned.
I would be extremely grateful to anyone who can give me some guidance on whether I can create a query or PL/SQL procedure to set this report up. I am fine with getting the info for only one role as well, because they are usually not more than 10 and repeating the query/procedure 10 times is not going to be a problem for me.
I have moderate experience with SQL and very little experience with PL/SQL, but enough to patch something up if given good pointers.
Thanks for taking the time to read this. :)
Upvotes: 0
Views: 1164
Reputation: 1560
If you're using 11g or later version of Oracle, the easiest way to transpose rows into columns is PIVOT operator (documentation). For example, consider this query - it'll give you the table of all roles and privileges with corresponding values for them:
SELECT * FROM
(SELECT fj.role_name, fj.priv_name, DECODE(rp.val,1,'YES',0,'NO','-') VAL
FROM (SELECT r.id role_id, r.name role_name, p.id priv_id, p.name priv_name
FROM ROLE r, PRIVILEGE p) fj
LEFT JOIN ROLE_PRIV rp ON fj.role_id = rp.role_id AND fj.priv_id = rp.priv_id)
PIVOT (MAX(VAL) FOR PRIV_NAME IN (list_of_privileges))
ORDER BY ROLE_NAME
But there is a catch with such solution: you need to name each privilige in IN clause (or use XML keyword, but it'll mess up the result), but, considering that list of priviliges supposed to change not so frequently, it shouldn't be a problem. The following example:
WITH ROLE AS (SELECT 1 ID, 'ROLE1' NAME FROM DUAL
UNION
SELECT 2 ID, 'ROLE2' NAME FROM DUAL
UNION
SELECT 3 ID, 'ROLE3' NAME FROM DUAL),
PRIVILEGE AS (SELECT 1 ID, 'READ' NAME FROM DUAL
UNION
SELECT 2 ID, 'WRITE' NAME FROM DUAL
UNION
SELECT 3 ID, 'EXECUTE' NAME FROM DUAL),
ROLE_PRIV AS (SELECT 1 ROLE_ID, 1 PRIV_ID, 1 VAL FROM DUAL
UNION
SELECT 1 ROLE_ID, 2 PRIV_ID, 0 VAL FROM DUAL
UNION
SELECT 2 ROLE_ID, 1 PRIV_ID, 1 VAL FROM DUAL
UNION
SELECT 2 ROLE_ID, 2 PRIV_ID, 0 VAL FROM DUAL
UNION
SELECT 3 ROLE_ID, 1 PRIV_ID, 0 VAL FROM DUAL
UNION
SELECT 3 ROLE_ID, 3 PRIV_ID, 1 VAL FROM DUAL)
SELECT * FROM
(SELECT fj.role_name, fj.priv_name, DECODE(rp.val,1,'YES',0,'NO','-') VAL
FROM (SELECT r.id role_id, r.name role_name, p.id priv_id, p.name priv_name
FROM ROLE r, PRIVILEGE p) fj
LEFT JOIN ROLE_PRIV rp ON fj.role_id = rp.role_id AND fj.priv_id = rp.priv_id)
PIVOT (MAX(VAL) FOR PRIV_NAME IN ('READ','WRITE','EXECUTE'))
ORDER BY ROLE_NAME
Will give you the following result:
ROLE_NAME 'READ' 'WRITE' 'EXECUTE'
ROLE1 YES NO -
ROLE2 YES NO -
ROLE3 NO - YES
If you're using the earlier version of Oracle, you can consider alternative solutions (like this one).
Well, if you can't use PIVOT operation, the simplest way to get the desired output would be this query:
SELECT p.name, DECODE((SELECT VAL
FROM ROLE_PRIV rp
WHERE rp.role_id = (SELECT ID
FROM ROLE r
WHERE r.name = :ROLE_NAME)
AND rp.priv_id = p.id),
1, 'YES',
0, 'NO',
'-') :ROLE_NAME
FROM PRIVILEGE p
You can add addtional roles to this query by just adding another column. For example, take a look at this query:
WITH ROLE AS (SELECT 1 ID, 'ROLE1' NAME FROM DUAL
UNION
SELECT 2 ID, 'ROLE2' NAME FROM DUAL
UNION
SELECT 3 ID, 'ROLE3' NAME FROM DUAL),
PRIVILEGE AS (SELECT 1 ID, 'READ' NAME FROM DUAL
UNION
SELECT 2 ID, 'WRITE' NAME FROM DUAL
UNION
SELECT 3 ID, 'EXECUTE' NAME FROM DUAL),
ROLE_PRIV AS (SELECT 1 ROLE_ID, 1 PRIV_ID, 1 VAL FROM DUAL
UNION
SELECT 1 ROLE_ID, 2 PRIV_ID, 0 VAL FROM DUAL
UNION
SELECT 2 ROLE_ID, 1 PRIV_ID, 1 VAL FROM DUAL
UNION
SELECT 2 ROLE_ID, 2 PRIV_ID, 1 VAL FROM DUAL
UNION
SELECT 3 ROLE_ID, 1 PRIV_ID, 0 VAL FROM DUAL
UNION
SELECT 3 ROLE_ID, 3 PRIV_ID, 1 VAL FROM DUAL)
SELECT p.name,
DECODE((SELECT VAL
FROM ROLE_PRIV rp
WHERE rp.role_id = (SELECT ID
FROM ROLE r
WHERE r.name = 'ROLE1')
AND rp.priv_id = p.id),
1, 'YES',
0, 'NO',
'-') ROLE1,
DECODE((SELECT VAL
FROM ROLE_PRIV rp
WHERE rp.role_id = (SELECT ID
FROM ROLE r
WHERE r.name = 'ROLE2')
AND rp.priv_id = p.id),
1, 'YES',
0, 'NO',
'-') ROLE2,
DECODE((SELECT VAL
FROM ROLE_PRIV rp
WHERE rp.role_id = (SELECT ID
FROM ROLE r
WHERE r.name = 'ROLE3')
AND rp.priv_id = p.id),
1, 'YES',
0, 'NO',
'-') ROLE3
FROM PRIVILEGE p
It will provide you result for three roles.
Something like this should work:
SELECT p.name,
DECODE((SELECT COUNT(1)
FROM ROLE_PRIV rp
WHERE rp.role_id = (SELECT ID
FROM ROLE r
WHERE r.name = 'ROLE1')
AND rp.priv_id = p.id),
0, 'NO',
'YES') ROLE1
FROM PRIVILEGE p
Upvotes: 2