This tutorial explains how the Rows to String solution presented in Hierarchical Method works. Note: If you are unfamiliar with Oracle's hierarchical features you should review the tutorials at SQL Features Tutorials: Hierarchical Data tutorial before proceeding.
The basic solution in the preceding tutorial looked like this.
set null "(null)" select tb.group_key , substr ( sys_connect_by_path( tb.val, ',' ) , 2 ) as string from ( select group_key , val , row_number() over ( partition by group_key order by val ) as val_index from t ) tb where connect_by_isleaf = 1 connect by val_index = prior val_index + 1 and group_key = prior group_key start with val_index = 1 ;
GROUP_KEY STRING ---------- --------------- Group 1 a Group 2 a,b Group 3 a,b,c Group 4 a,a,b,b Group 5 a,b,d,e, Group 6 (null) Group 7 , Group 8 a,a,b,b,, (null) (null)
To understand how this query works we will consider its individual components. First, look at the results of the FROM clause subquery.
break on group_key skip 1 select group_key , val , row_number() over ( partition by group_key order by val ) as val_index from t ;
GROUP_KEY VAL VAL_INDEX ---------- ---------- ---------- Group 1 a 1 Group 2 a 1 b 2 Group 3 a 1 b 2 c 3 Group 4 a 1 a 2 b 3 b 4 Group 5 a 1 b 2 d 3 e 4 (null) 5 Group 6 (null) 1 Group 7 (null) 1 (null) 2 Group 8 a 1 a 2 b 3 b 4 (null) 5 (null) 6 (null) (null) 1
VAL_INDEX gives us a column we can use to hierarchically connect these rows, like this.
select tb.group_key , tb.val , tb.val_index , SYS_CONNECT_BY_PATH( tb.val, ',' ) connect_by_path from ( select group_key , val , row_number() over ( partition by group_key order by val ) as val_index from t ) tb CONNECT BY VAL_INDEX = PRIOR VAL_INDEX + 1 AND GROUP_KEY = PRIOR GROUP_KEY START WITH VAL_INDEX = 1 ;
GROUP_KEY VAL VAL_INDEX CONNECT_BY_PATH ---------- ---------- ---------- --------------- Group 1 a 1 ,a Group 2 a 1 ,a b 2 ,a,b Group 3 a 1 ,a b 2 ,a,b c 3 ,a,b,c Group 4 a 1 ,a a 2 ,a,a b 3 ,a,a,b b 4 ,a,a,b,b Group 5 a 1 ,a b 2 ,a,b d 3 ,a,b,d e 4 ,a,b,d,e (null) 5 ,a,b,d,e, Group 6 (null) 1 , Group 7 (null) 1 , (null) 2 ,, Group 8 a 1 ,a a 2 ,a,a b 3 ,a,a,b b 4 ,a,a,b,b (null) 5 ,a,a,b,b, (null) 6 ,a,a,b,b,, (null) (null) 1 ,
Once the rows are hierarchically connected we see that the CONNECT_BY_PATH values of the leaf rows are very close to the result we want. To identify these rows we can use the CONNECT_BY_ISLEAF function.
select tb.group_key , tb.val , tb.val_index , sys_connect_by_path( tb.val, ',' ) connect_by_path , CONNECT_BY_ISLEAF from ( select group_key , val , row_number() over ( partition by group_key order by val ) as val_index from t ) tb connect by val_index = prior val_index + 1 and group_key = prior group_key start with val_index = 1 ;
GROUP_KEY VAL VAL_INDEX CONNECT_BY_PATH CONNECT_BY_ISLEAF ---------- ---------- ---------- --------------- ----------------- Group 1 a 1 ,a 1 Group 2 a 1 ,a 0 b 2 ,a,b 1 Group 3 a 1 ,a 0 b 2 ,a,b 0 c 3 ,a,b,c 1 Group 4 a 1 ,a 0 a 2 ,a,a 0 b 3 ,a,a,b 0 b 4 ,a,a,b,b 1 Group 5 a 1 ,a 0 b 2 ,a,b 0 d 3 ,a,b,d 0 e 4 ,a,b,d,e 0 (null) 5 ,a,b,d,e, 1 Group 6 (null) 1 , 1 Group 7 (null) 1 , 0 (null) 2 ,, 1 Group 8 a 1 ,a 0 a 2 ,a,a 0 b 3 ,a,a,b 0 b 4 ,a,a,b,b 0 (null) 5 ,a,a,b,b, 0 (null) 6 ,a,a,b,b,, 1 (null) (null) 1 , 1
Filtering out the non-leaf rows and stripping the leading "," in CONNECT_BY_PATH gives us our final solution.
select tb.group_key , tb.val_index , SUBSTR -- strips the leading "," ( sys_connect_by_path( tb.val, ',' ) , 2 ) as string from ( select group_key , val , row_number() over ( partition by group_key order by val ) as val_index from t ) tb where CONNECT_BY_ISLEAF = 1 -- selects only leaf rows connect by val_index = prior val_index + 1 and group_key = prior group_key start with val_index = 1 ;
GROUP_KEY VAL_INDEX STRING ---------- ---------- --------------- Group 1 1 a Group 2 2 a,b Group 3 3 a,b,c Group 4 4 a,a,b,b Group 5 5 a,b,d,e, Group 6 1 (null) Group 7 2 , Group 8 6 a,a,b,b,, (null) 1 (null)