SQL Challenge/Puzzle: How to create an ASCII art hierarchy tree with an SQL query?

匆匆过客 提交于 2019-12-13 06:38:12

问题


The initial motivation for this one, was to display Oracles' actual execution plans, saved in GV$SQL_PLAN, in a visual, clear way.


  • I've attached my suggested solutions.
    • Please feel free to add yours, as long as it fulfills the requirements.
    • Please mention the database name your solution was written for.

Requirements

Input

  • A table containing columns "id" (node's id) and "pid" (node's parent id).

Output

  • The result should be a an ASCII art graph (see example below)
    • Each pair of "id" and "pid" nodes should be connected with an edge.
    • The root node might have an additional single edge.
    • There should be no other edged especially not edges that are not connected to any node in one of their sides.

Code

  • A single SELECT statement based only on native SQL
    • No UDF (User Defined Functions).
    • No T-SQL, PL/SQL etc.

Sample data

create table h (id int,pid int);

insert into h (id,pid) values (0  ,null);
insert into h (id,pid) values (1  ,0   );
insert into h (id,pid) values (2  ,1   );
insert into h (id,pid) values (3  ,2   );
insert into h (id,pid) values (4  ,3   );
insert into h (id,pid) values (5  ,4   );
insert into h (id,pid) values (6  ,3   );
insert into h (id,pid) values (7  ,6   );
insert into h (id,pid) values (8  ,7   );
insert into h (id,pid) values (9  ,8   );
insert into h (id,pid) values (10 ,9   );
insert into h (id,pid) values (11 ,10  );
insert into h (id,pid) values (12 ,9   );
insert into h (id,pid) values (13 ,12  );
insert into h (id,pid) values (14 ,8   );
insert into h (id,pid) values (15 ,6   );
insert into h (id,pid) values (16 ,15  );
insert into h (id,pid) values (17 ,6   );
insert into h (id,pid) values (18 ,17  );
insert into h (id,pid) values (19 ,17  );
insert into h (id,pid) values (20 ,3   );
insert into h (id,pid) values (21 ,20  );
insert into h (id,pid) values (22 ,21  );
insert into h (id,pid) values (23 ,22  );
insert into h (id,pid) values (24 ,21  );

Results

Vertical siblings

|
|____ 1
     |
     |____ 2
          |
          |____ 3
               |
               |____ 4
               |    |
               |    |____ 5
               |
               |____ 6
               |    |
               |    |____ 7
               |    |    |
               |    |    |____ 8
               |    |         |
               |    |         |____ 9
               |    |         |    |
               |    |         |    |____ 10
               |    |         |    |    |
               |    |         |    |    |____ 11
               |    |         |    |
               |    |         |    |____ 12
               |    |         |         |
               |    |         |         |____ 13
               |    |         |
               |    |         |____ 14
               |    |
               |    |____ 15
               |    |    |
               |    |    |____ 16
               |    |
               |    |____ 17
               |         |
               |         |____ 18
               |         |
               |         |____ 19
               |
               |____ 20
                    |
                    |____ 21
                         |
                         |____ 22
                         |    |
                         |    |____ 23
                         |
                         |____ 24

Horizontal siblings

                      |                      
                      |                      
                      |                      
                      0                      
                      |                      
                      |                      
                      |                      
                      |                      
                      |                      
                      1                      
                      |                      
                      |                      
                      |                      
                      |                      
                      |                      
                      2                      
                      |                      
                      |                      
                      |                      
                      |                      
                      |                      
                      3                      
                      |                      
                      |                      
  ---------------------------------------    
  |                 |                   |    
  |                 |                   |    
  4                 6                   20   
  |                 |                   |    
  |                 |                   |    
  |         -------------------         |    
  |         |         |       |         |    
  |         |         |       |         |    
  5         7         15      17        21   
            |         |       |         |    
            |         |       |         |    
            |         |    ------    ------  
            |         |    |    |    |    |  
            |         |    |    |    |    |  
            8         16   18   19   22   24 
            |                        |       
            |                        |       
          --------                   |  
          |      |                   |  
          |      |                   |  
          9      14                  23 
          |                             
          |                             
       ------  
       |    |  
       |    |  
       10   12 
       |    |  
       |    |  
       |    |  
       |    |  
       |    |  
       11   13 

回答1:


Oracle

Vertical siblings

with        last_sibling (id)
            as
            (
                select      max (id)
                from        h
                group by    pid
            )

           ,tree (id,pid,branch) as
            (
                 select     h.id
                           ,h.pid
                           ,cast ('' as varchar (4000))

                 from       h

                 where      h.pid is null

                 union all

                 select     h.id
                           ,h.pid
                           ,t.branch || case when ls.id is not null then ' ' else '|' end || '    '

                 from                   tree            t

                            left join   last_sibling    ls

                            on          ls.id   =
                                        t.id

                            join        h

                            on          h.pid =
                                        t.id

                    ) search depth first by id set order_by_id

           ,vertical_space (n)
            as
            (
                select      1
                from        dual

                union all

                select      vs.n + 1
                from        vertical_space  vs
                where       vs.n - 1 < 1
            )

select      t.branch || case vs.n when 1 then '|____' || ' ' || cast (t.id as varchar(10)) else '|' end

from                    tree            t

            cross join  vertical_space  vs

order by    t.order_by_id
           ,vs.n desc
;



回答2:


Oracle

Horizontal siblings

with        il_parameters (atomic_width) as
            (
                 select     5   

                 from       dual
            )

select      decode
            (
                 n.n      

                ,1

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (decode (h.n_within_parent,1,decode (h.n_desc_within_parent,1,'|','-'),'-'),h.width/2 * il_parameters.atomic_width + 1,decode (h.n_within_parent,1,' ','-')),h.width * il_parameters.atomic_width,decode (h.n_desc_within_parent,1,' ','-'))
                 ) within group (order by h.id)                               

                ,2

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (' ',h.width/2 * il_parameters.atomic_width,' ') || '|' ,h.width * il_parameters.atomic_width)
                 ) within group (order by h.id)                        

                ,3

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (' ',h.width/2 * il_parameters.atomic_width,' ') || '|' ,h.width * il_parameters.atomic_width)
                 ) within group (order by h.id)    

                ,4

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (' ',h.width/2 * il_parameters.atomic_width,' ') || h.id,h.width * il_parameters.atomic_width)
                 ) within group (order by h.id)     

                ,5

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (' ',h.width/2 * il_parameters.atomic_width,' ') || decode (h.isleaf,0,'|',' ') ,h.width * il_parameters.atomic_width)
                 ) within group (order by h.id)   

                ,6

                ,listagg 
                 (
                      lpad (' ',x_lpad * il_parameters.atomic_width) 
                   || rpad (lpad (' ',h.width/2 * il_parameters.atomic_width,' ') || decode (h.isleaf,0,'|',' ') ,h.width * il_parameters.atomic_width)
                 ) within group (order by h.id)                                                                     
            )                                                           graph



from                       (select          h.id                                                                                 id
                                           ,h.width                                                                              width
                                           ,h.y                                                                                  y
                                           ,h.isleaf                                                                             isleaf
                                           ,h.n_within_parent                                                                    n_within_parent
                                           ,h.n_desc_within_parent                                                               n_desc_within_parent                                                                       

                                           ,     sum (regexp_substr (heirarchy_x_within_parent,'\d+',1,n.n))
                                               - lag (sum (regexp_substr (heirarchy_x_within_parent,'\d+',1,n.n)),1,0) over
                                                 (
                                                      partition by   h.y

                                                      order by       h.id
                                                 ) 
                                               - lag (h.width,1,0) over
                                                 (
                                                      partition by   h.y

                                                      order by       h.id
                                                 )                                                                               x_lpad



                            from                           (select      h.id
                                                                       ,h.width
                                                                       ,h.n_within_parent
                                                                       ,h.n_desc_within_parent
                                                                       ,level                                        y
                                                                       ,sys_connect_by_path (x_within_parent,',')    heirarchy_x_within_parent
                                                                       ,connect_by_isleaf                            isleaf                          

                                                            from       (select      h.id                                              id
                                                                                   ,h.pid                                             pid
                                                                                   ,count (*)                                         width

                                                                                   ,nvl
                                                                                    (
                                                                                         sum (count (*)) over
                                                                                         (
                                                                                              partition by   h.pid

                                                                                              order by       h.id

                                                                                              rows between   unbounded preceding
                                                                                                   and       1 preceding 
                                                                                         )
                                                                                        ,0
                                                                                    )                                                 x_within_parent

                                                                                   ,row_number () over
                                                                                    (
                                                                                         partition by   h.pid

                                                                                         order by       h.id
                                                                                    )                                                 n_within_parent

                                                                                   ,row_number () over
                                                                                    (
                                                                                         partition by   h.pid

                                                                                         order by       h.id desc
                                                                                    )                                                 n_desc_within_parent

                                                                        from       (select      connect_by_root h.id          id  
                                                                                               ,connect_by_root h.pid         pid                                                       

                                                                                    from        h

                                                                                    where       connect_by_isleaf = 1

                                                                                    connect by  h.pid = prior h.id
                                                                                    ) h

                                                                        group by    h.id 
                                                                                   ,h.pid
                                                                        ) h

                                                            connect by  h.pid = prior h.id

                                                            start with  h.pid is null 
                                                            ) h

                                            join           (select      rownum n 
                                                            from        dual 
                                                            connect by  level <= 100
                                                           ) n

                                            on              n.n <=
                                                            regexp_count (h.heirarchy_x_within_parent,',') 

                            group by        h.id  
                                           ,h.width       
                                           ,h.y 
                                           ,h.isleaf
                                           ,h.n_within_parent                                                                    
                                           ,h.n_desc_within_parent                                                                          
                            ) h     

            cross join      il_parameters

            cross join     (select      rownum n 
                            from        dual 
                            connect by  level <= 6
                           )  n

group by    h.y  
           ,n.n

order by    h.y 
           ,n.n                                                                                                                                       
;      



回答3:


SQLite

Vertical siblings

with        last_sibling (id)
            as
            (
                select      max (id)
                from        h
                group by    pid
            )

           ,tree (id,branch,path)
            as
            (
                select      1       as id
                           ,''      as branch
                           ,'001'   as path

                union all

                select      h.id
                           ,t.branch || case when ls.id is not null then ' ' else '|' end || '    '
                           ,t.path || '_' || substr ('00000' || h.id,-5)

                from                    tree            t

                            left join   last_sibling    ls

                            on          ls.id   =
                                        t.id

                            join        h

                            on          h.pid =
                                        t.id
            )

           ,vertical_space (n)
            as
            (
                select      1

                union all

                select      vs.n + 1
                from        vertical_space  vs
                where       vs.n < 2
            )

select      t.branch || case vs.n when 1 then '|____' || ' ' || cast (t.id as text) else '|' end

from                    tree            t

            cross join  vertical_space  vs

order by    t.path
           ,vs.n desc
;



回答4:


SQL Server

Vertical siblings

with        last_sibling (id)
            as
            (
                select      max (id)
                from        h
                group by    pid
            )

           ,tree (id,branch,path)
            as
            (
                select      h.id
                           ,cast ('' as varchar(max))
                           ,cast (right (replicate ('0',10) + cast (h.id as varchar(10)),10) as varchar(max))

                from        h

                where       h.pid is null

                union all

                select      h.id
                           ,t.branch + case when (select 1 xxxfrom last_sibling ls where ls.id = t.id) = 1 then ' ' else '|' end + '    '
                           ,t.path + '_' + right (replicate ('0',10) + cast (h.id as varchar(10)),10)

                from                    tree            t

                            join        h

                            on          h.pid =
                                        t.id
            )

           ,vertical_space (n)
            as
            (
                select      1

                union all

                select      vs.n + 1
                from        vertical_space  vs
                where       vs.n < 2
            )

select      t.branch + case vs.n when 1 then '|____' + ' ' + cast (t.id as varchar(10)) else '|' end

from                    tree            t

            cross join  vertical_space  vs

order by    t.path
           ,vs.n desc

option      (maxrecursion 0)
;



回答5:


Teradata

Vertical siblings

with        recursive tree (id,branch,path)
            as
            (
                select      1                               as id
                           ,cast (''    as varchar (1000))  as branch
                           ,cast ('001' as varchar (1000))  as path

                from        (select 'x') x(x)

                union all

                select      h.id
                           ,t.branch || case when ls.id is not null then ' ' else '|' end || '    '
                           ,t.path || '_' || substr ('00000' || h.id,-5)

                from                    tree            t

                            left join   last_sibling    ls

                            on          ls.id   =
                                        t.id

                            join        h

                            on          h.pid =
                                        t.id
            )

           ,last_sibling (id)
            as
            (
                select      max (id)
                from        h
                group by    pid
            )

           ,recursive vertical_space (n)
            as
            (
                select      1

                from        (select 'x') x(x)

                union all

                select      vs.n + 1
                from        vertical_space  vs
                where       vs.n < 2
            )

select      t.branch || case vs.n when 1 then '|____' || ' ' || cast (t.id as varchar(10)) else '|' end

from                    tree            t

            cross join  vertical_space  vs

order by    t.path
           ,vs.n desc
;


来源:https://stackoverflow.com/questions/39925938/sql-challenge-puzzle-how-to-create-an-ascii-art-hierarchy-tree-with-an-sql-quer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!