views:

378

answers:

4

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?

+2  A: 

You could take a look at http://www.adp-gmbh.ch/ora/sql/connect%5Fby.html

regjo
A: 

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;
Henry Gao
+1  A: 

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.

egorius
+3  A: 

Hi cafe,

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
Vincent Malgrat
+1 Didn't know about connect_by_root. My version works in 9i, but yours is way better.
egorius