Using Oracle 10g. I have two tables:
User Parent
-------------
1 (null)
2 1
3 1
4 3
Permission User_ID
-------------------
A 1
B 3
The values in the permissions table get inherited down to the children. I would like to write a single query that could return me something like this:
User Permission
------------------
1 A
2 A
3 A
3 A
3 B
4 A
4 B
Is it possible to formulate such a query using 10g connect .. by syntax to pull in rows from previous levels?
you can achieve the desired result with a connect by (and the function CONNECT_BY_ROOT
that returns the column value of the root node):
SQL> WITH users AS (
2 SELECT 1 user_id, (null) PARENT FROM dual
3 UNION ALL SELECT 2, 1 FROM dual
4 UNION ALL SELECT 3, 1 FROM dual
5 UNION ALL SELECT 4, 3 FROM dual
6 ), permissions AS (
7 SELECT 'A' permission, 1 user_id FROM dual
8 UNION ALL SELECT 'B', 3 FROM dual
9 )
10 SELECT lpad('*', 2 * (LEVEL-1), '*')||u.user_id u,
11 u.user_id, connect_by_root(permission) permission
12 FROM users u
13 LEFT JOIN permissions p ON u.user_id = p.user_id
14 CONNECT BY u.PARENT = PRIOR u.user_id
15 START WITH p.permission IS NOT NULL
16 ORDER SIBLINGS BY user_id;
U USER_ID PERMISSION
--------- ------- ----------
3 3 B
**4 4 B
1 1 A
**2 2 A
**3 3 A
****4 4 A
You could take a look at http://www.adp-gmbh.ch/ora/sql/connect_by.html
Kind of black magic, but you can use table-cast-multiset to reference one table from another in WHERE clause:
create table t1(
usr number,
parent number
);
create table t2(
usr number,
perm char(1)
);
insert into t1 values (1,null);
insert into t1 values (2,1);
insert into t1 values (3,1);
insert into t1 values (4,3);
insert into t2 values (1,'A');
insert into t2 values (3,'B');
select t1.usr
, t2.perm
from t1
, table(cast(multiset(
select t.usr
from t1 t
connect by t.usr = prior t.parent
start with t.usr = t1.usr
) as sys.odcinumberlist)) x
, t2
where t2.usr = x.column_value
;
In the subquery x
I construct a table of all parents for the given user from t1
(including itself), then join it with permissions for these parents.
Here is a example for just one user id. you can use proc to loop all.
CREATE TABLE a_lnk
(user_id VARCHAR2(5),
parent_id VARCHAR2(5));
CREATE TABLE b_perm
(perm VARCHAR2(5),
user_id VARCHAR2(5));
INSERT INTO a_lnk
SELECT 1, NULL
FROM DUAL;
INSERT INTO a_lnk
SELECT 2, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 3, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 4, 3
FROM DUAL;
INSERT INTO b_perm
SELECT 'A', 1
FROM DUAL;
INSERT INTO b_perm
SELECT 'B', 3
FROM DUAL;
-- example for just for user id = 1
--
SELECT c.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.user_id
UNION
SELECT d.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.parent_id;