Oracle SQL connect_by_root和子查询

时间:2016-11-04 14:25:27

标签: sql oracle

过去几天我正在为我的任务制定解决方案,而我似乎无法找到答案。

简介:假设我们有一个名为的表。每个人都有三个字段:namebosspositionname是主键,position只是一个字符串,boss作为外键指向不同的人name。它创建了一个普通的树,如Person1 - > Person2 - > Person3 - > Person4,其中Person4是最高老板而Person1是根。为简单起见,我们假设没有人拥有超过3个老板,而Person4是首席老板(唯一一个boss等于null的人。)

示例路径:

Person7 - > Person4

Person6 - > Person8 - > Person4

Person2 - > Person8 - > Person4

所以我的作业说:创建一个查询,按照层级顺序显示每个人的名字,每个人position等于“工人”或“经理”仅使用树操作(连接) by,connect_by_root等)和子查询

输出表必须包含5列:

Name | Position | Boss 1 | Boss 2 | Boss 3

如果任何boss列为null,那么我应该插入一些空格。

这是我目前的查询:

select  
  case    
    when l = 1 then name else '    ' end as "Name",
  position,
  case 
    when l = 2 then name else '    ' end as "Boss 1",
  case
    when l = 3 then name else '    ' end as "Boss 2",
  case 
    when l = 4 then name else '    ' end as "Boss 3"    
from (
  select
    connect_by_root position as position, 
    level as l,
    name 
  from 
    People
  connect by prior
    boss = name
  start with 
    position = 'Worker'
    or position = 'Manager'
  );

它有点诀窍,但树的每个级别都是一个新行,这是我必须避免的。我知道为什么这个查询产生这样的结果,但我不知道如何让它遍历树而不在每一步创建新行。

我的结果:

Name |Position|Boss 1|Boss 2|Boss 3

JOHN    WORKER                  
        WORKER  HENRY               
        WORKER          PETER       
TERRY   WORKER                      
        WORKER  PETER               
ALICE   WORKER                  
        WORKER  PETER               
BILL    MANAGER                     
        MANAGER JAMES               
        MANAGER         PETER     

这是我想要达到的结果:

Name |Position|Boss 1|Boss 2|Boss 3

JOHN    WORKER  HENRY  PETER        

TERRY   WORKER  PETER                   

ALICE   WORKER  PETER                   

BILL    MANAGER JAMES  PETER                

是否有任何解决方案没有使用像Pivot这样的复杂功能来使其工作?

2 个答案:

答案 0 :(得分:0)

可以在不需要任何枢轴等的情况下完成此操作,纯粹使用CONNECT_BY_ROOTCONNECT_BY_ISLEAFSYS_CONNECT_BY_PATH,以及明智地使用REGEXP_SUBSTR

WITH people AS (SELECT 'JOHN' name, 'WORKER' position, 'HENRY' boss FROM dual UNION ALL
                SELECT 'HENRY' name, 'CFO' position, 'PETER' boss FROM dual UNION ALL
                SELECT 'TERRY' name, 'WORKER' position, 'PETER' boss FROM dual UNION ALL
                SELECT 'ALICE' name, 'WORKER' position, 'PETER' boss FROM dual UNION ALL
                SELECT 'JAMES' name, 'CIO' position, 'PETER' boss FROM dual UNION ALL
                SELECT 'FRED' name, 'MANAGER' position, NULL boss FROM dual UNION ALL
                SELECT 'BILL' name, 'MANAGER' position, 'JAMES' boss FROM dual UNION ALL
                SELECT 'PETER' name, 'CEO' position, 'FRED' boss FROM dual)
-- end of mimicking your people table with some sample data in it
-- you wouldn't need the above, just use the query below:
SELECT connect_by_root name AS name,
       connect_by_root position AS position,
       regexp_substr(sys_connect_by_path(boss, '>'), '[^>]+', 1, 1) boss1,
       regexp_substr(sys_connect_by_path(boss, '>'), '[^>]+', 1, 2) boss2,
       regexp_substr(sys_connect_by_path(boss, '>'), '[^>]+', 1, 3) boss3
FROM   people
WHERE  connect_by_isleaf = 1
CONNECT BY PRIOR boss = name
START WITH position IN ('WORKER', 'MANAGER');

NAME  POSITION BOSS1 BOSS2 BOSS3
----- -------- ----- ----- -----
ALICE WORKER   PETER FRED  
BILL  MANAGER  JAMES PETER FRED
FRED  MANAGER              
JOHN  WORKER   HENRY PETER FRED
TERRY WORKER   PETER FRED  

CONNECT_BY_ISLEAF确定该行是否为叶子行(1)或不是(0)。因此,就像您可以使用CONNECT_BY_ROOT标识根值一样,您可以使用CONNECT_BY_ISLEAF告诉哪一行是结束行。

SYS_CONNECT_BY_PATH生成到目前为止所有值的路径。因此,在叶子行上,它将包含所有必需的值。然后我们可以解析这个生成的字符串,以获得不包含路径分隔符的第一个,第二个等部分。

答案 1 :(得分:0)

它应该像这样工作(我没有要检查的源数据):

select  
  root_name as "Name",
  max(position),
  max(case when l = 2 then name else null end) as "Boss 1",
  max(case when l = 3 then name else null end) as "Boss 2",
  max(case when l = 4 then name else null end) as "Boss 3"    
from (
  select
    connect_by_root position as position, 
    connect_by_root name as root_name, 
    level as l,
    name 
  from 
    People
  connect by prior
    boss = name
  start with 
    position = 'Worker'
    or position = 'Manager'
  )
group by root_name;