1

I have a table, SELECT * FROM data

id   pred   name        visual link, for your convenience
--------------------
 1   null   One              
20   null   Two         <--+
21     20   Three         -^
30   null   Four              <--+      
31     30   Five                -^  <--+ 
32     31   Six                       -^  

In which the rows are connected via the pred column to the id column. They form only chains and not a tree -- each node has only one or zero successors (if that's important).

I want to add a column init to the query where the very first element in the chain is shown, i.e.

id   pred   name    init initname
---------------------------------
 1   null   One        1 One
20   null   Two       20 Two
21     20   Three     20 Two
30   null   Four      30 Four
31     30   Five      30 Four
32     31   Six       30 Four
  • It would be ok if rows with pred=null also show null for init.
  • The initname column is completely optional and I show it here only for demonstration, I only need the id.

From what I gathered about the connect by clause I managed a somewhat reverse result, where for each "root" its "child" nodes are listed. I don't know how to "turn the query around".

SELECT id, pred, CONNECT_BY_ROOT id init, LEVEL, CONNECT_BY_ISLEAF "IsLeaf"
FROM data
CONNECT BY PRIOR pred=id
ORDER BY id, level;

Gives the result

id  pred   init lvl isLeaf
--------------------------
1   null    1   1   1
20  null    20  1   1
20  null    21  2   1
21  20      21  1   0
30  null    30  1   1
30  null    31  2   1
30  null    32  3   1
31  30      31  1   0
31  30      32  2   0
32  31      32  1   0

which somehow represents the the whole "tree", obviously. But alas, "the wrong way around". I'd need for example

id  pred  init lvl isLeaf
21   20    0     ?   ?

instead of

id  pred init lvl isLeaf
21   20   21   1     0

If you need data, here is the example data:

create table data ( id number primary key, pred number, name varchar2(100) );
insert into data(id,pred,name) values(     1 ,  null  , 'One');
insert into data(id,pred,name) values(    20,   null  , 'Two');
insert into data(id,pred,name) values(21,     20  , 'Three');
insert into data(id,pred,name) values(30,   null ,  'Four');
insert into data(id,pred,name) values(31,     30 ,  'Five');
insert into data(id,pred,name) values(32,     31 ,  'Six');
towi
  • 21,587
  • 28
  • 106
  • 187

3 Answers3

3
SQL>  select id
  2        , pred
  3        , name
  4        , connect_by_root id             init
  5        , connect_by_root name           initname
  6        , sys_connect_by_path(id,' -> ') scbp
  7     from data
  8  connect by prior id = pred
  9    start with pred is null
 10  /

        ID       PRED NAME             INIT INITNAME   SCBP
---------- ---------- ---------- ---------- ---------- ------------------------------
         1            One                 1 One         -> 1
        20            Two                20 Two         -> 20
        21         20 Three              20 Two         -> 20 -> 21
        30            Four               30 Four        -> 30
        31         30 Five               30 Four        -> 30 -> 31
        32         31 Six                30 Four        -> 30 -> 31 -> 32

6 rows selected.
Rob van Wijk
  • 17,555
  • 5
  • 39
  • 55
  • You mean, my mistake has been to use `connect by prior pred=id` instead of `connect by prior id=pred`? Whow. – towi Sep 29 '14 at 09:42
  • Yes. Others have tried to tell you, but seeing is believing :-) – Rob van Wijk Sep 29 '14 at 09:48
  • Yes, I did not notice the difference. I did not expect the order is important, so I did not "look close enough". Now that I "have seen" it is of course obvious that the order in fact *is* important. – towi Sep 29 '14 at 11:31
1

Please try to use following expression to get name of the root element:

substr(SYS_CONNECT_BY_PATH(name, '/'), instr(SYS_CONNECT_BY_PATH(name, '/'), '/', -1)+1)

Replace delimiter '/' if needed.

SELECT id, pred, CONNECT_BY_ROOT id init, LEVEL, CONNECT_BY_ISLEAF "IsLeaf", 
       substr(SYS_CONNECT_BY_PATH(name, '/'), instr(SYS_CONNECT_BY_PATH(name, '/'), '/', -1)+1)
FROM data
CONNECT BY PRIOR pred=id
--    START WITH pred is NULL --???
ORDER BY id, level;

You probably need to add START WITH clause as well

Rusty
  • 1,988
  • 10
  • 12
  • No, that gives me `1,null,1`, `20,null,20`, `30,null,30`, plus the level and isleaf info. Still, I think it is "the wrong way around". For example, I need the result for all the rows and not for only the `pred is null` rows. – towi Sep 26 '14 at 08:55
  • I mean you anyway have to have this clause otherwise you will see full tree and all subtrees (duplications), especially if you insert more data. – Rusty Sep 26 '14 at 08:58
  • An interesting approach. "start with" is still wrong. Your result contains everything I need and the unique rows I need are those with "max level". I could do that with a subclause and `group by`. I wonder if there is another `substr`-less solution, though... – towi Sep 26 '14 at 09:25
  • 1
    I don't think that you can refer on root element in hierarchal SQL somehow else because you don't know how deep your current row is in your tree (chain). – Rusty Sep 26 '14 at 09:35
  • My most recent research got me to `select id, pred, name, init, (select name from data where id=init) initname from ( SELECT d1.*, (select d2.id init from data d2 where CONNECT_BY_ISLEAF=1 start with d2.id=d1.id CONNECT BY PRIOR pred=id) init FROM data d1 ORDER BY id ) xdata;`, which looks quite good. I'd like to get rid of the proliferating subqueries, though. – towi Sep 26 '14 at 09:40
0

I managed to get the result with a nested query.

select id, pred, name, init, (select name from data where id=init) initname
from (
    SELECT d1.*
      , (select d2.id init
           from data d2
         where CONNECT_BY_ISLEAF=1 start with d2.id=d1.id CONNECT BY PRIOR pred=id) init
    FROM data d1
    ORDER BY id ) xdata
;

As you can see, to get the initname I need an additional subquery this way. This is not perfect, but good enough -- the query is on the PK.

id pred Name    init initname
------------------------------
1       One     1    One
20      Two     20   Two
21  20  Three   20   Two
30      Four    30   Four
31  30  Five    30   Four
32  31  Six     30   Four
towi
  • 21,587
  • 28
  • 106
  • 187