Rows to String

Hierarchical Method - Explained

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)
 



Linking to SQL Snippets ™

To link to this page in Oracle Technology Network Forums or OraFAQ Forums cut and paste this code.

  • [url=http://www.sqlsnippets.com/en/topic-12087.html]SQL Snippets: Rows to String - Hierarchical Method - Explained[/url]

To link to this page in HTML documents or Blogger comments cut and paste this code.

  • <a href="http://www.sqlsnippets.com/en/topic-12087.html">SQL Snippets: Rows to String - Hierarchical Method - Explained</a>

To link to this page in other web sites use the following values.

  • Link Text : SQL Snippets: Rows to String - Hierarchical Method - Explained
  • URL (href): http://www.sqlsnippets.com/en/topic-12087.html