简体   繁体   中英

How to get to the “final predecessor” with a CONNECT BY query?

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 , ie

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');
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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM