SQL Challenge/Puzzle:如何使用 SQL 查询创建 ASCII 艺术层次结构树?
SQL Challenge/Puzzle: How to create an ASCII art hierarchy tree with an SQL query?
这个的最初动机是以直观、清晰的方式显示 Oracles 的实际执行计划,保存在 GV$SQL_PLAN 中。
- 我附上了我建议的解决方案。
- 只要符合要求,请随意添加。
- 请提及您的解决方案所针对的数据库名称。
要求
输入
- A table 包含列 "id"(节点的 ID)和 "pid"(节点的父 ID)。
输出
- 结果应该是一个 ASCII 艺术图形(见下面的例子)
- 每对 "id" 和 "pid" 节点应与一条边相连。
- 根节点可能有一条额外的单边。
- 不应该有其他边,尤其是没有连接到其中一侧的任何节点的边。
代码
- 仅基于原生 SQL 的单个 SELECT 语句
- 无 UDF(用户定义函数)。
- 无T-SQL、PL/SQL等
示例数据
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 );
结果
垂直兄弟
|
|____ 1
|
|____ 2
|
|____ 3
|
|____ 4
| |
| |____ 5
|
|____ 6
| |
| |____ 7
| | |
| | |____ 8
| | |
| | |____ 9
| | | |
| | | |____ 10
| | | | |
| | | | |____ 11
| | | |
| | | |____ 12
| | | |
| | | |____ 13
| | |
| | |____ 14
| |
| |____ 15
| | |
| | |____ 16
| |
| |____ 17
| |
| |____ 18
| |
| |____ 19
|
|____ 20
|
|____ 21
|
|____ 22
| |
| |____ 23
|
|____ 24
同胞兄妹
|
|
|
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
SQLite
垂直兄弟
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
;
甲骨文
同胞兄妹
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
;
甲骨文
垂直兄弟
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
;
SQL 服务器
垂直兄弟
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)
;
天睿
垂直兄弟
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
;
这个的最初动机是以直观、清晰的方式显示 Oracles 的实际执行计划,保存在 GV$SQL_PLAN 中。
- 我附上了我建议的解决方案。
- 只要符合要求,请随意添加。
- 请提及您的解决方案所针对的数据库名称。
要求
输入
- A table 包含列 "id"(节点的 ID)和 "pid"(节点的父 ID)。
输出
- 结果应该是一个 ASCII 艺术图形(见下面的例子)
- 每对 "id" 和 "pid" 节点应与一条边相连。
- 根节点可能有一条额外的单边。
- 不应该有其他边,尤其是没有连接到其中一侧的任何节点的边。
代码
- 仅基于原生 SQL 的单个 SELECT 语句
- 无 UDF(用户定义函数)。
- 无T-SQL、PL/SQL等
示例数据
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 );
结果
垂直兄弟
|
|____ 1
|
|____ 2
|
|____ 3
|
|____ 4
| |
| |____ 5
|
|____ 6
| |
| |____ 7
| | |
| | |____ 8
| | |
| | |____ 9
| | | |
| | | |____ 10
| | | | |
| | | | |____ 11
| | | |
| | | |____ 12
| | | |
| | | |____ 13
| | |
| | |____ 14
| |
| |____ 15
| | |
| | |____ 16
| |
| |____ 17
| |
| |____ 18
| |
| |____ 19
|
|____ 20
|
|____ 21
|
|____ 22
| |
| |____ 23
|
|____ 24
同胞兄妹
|
|
|
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
SQLite
垂直兄弟
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
;
甲骨文
同胞兄妹
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
;
甲骨文
垂直兄弟
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
;
SQL 服务器
垂直兄弟
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)
;
天睿
垂直兄弟
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
;