员工与其经理之间的时间依赖关系

时间:2018-10-30 10:19:26

标签: sql oracle

我有一个如下所示的输入数据集,其中包含雇员与他的经理在2018年1月1日至2018年1月31日之间的关系。

输入数据集:

**EMP_ID    MGR_ID    FRM_DT         TO_DT**
EMP1      MGR1      01-JAN-2018    31-JAN-2018
EMP2      MGR2      01-JAN-2018    31-JAN-2018
EMP3      MGR3      01-JAN-2018    31-JAN-2018
EMP4      MGR4      01-JAN-2018    31-JAN-2018
EMP5      MGR5      01-JAN-2018    10-JAN-2018
EMP5      MGR1      11-JAN-2018    15-JAN-2018
EMP5      MGR2      16-JAN-2018    20-JAN-2018
EMP5      MGR3      21-JAN-2018    25-JAN-2018
EMP5      MGR4      26-JAN-2018    31-JAN-2018
EMP6      MGR6      01-JAN-2018    15-JAN-2018
EMP6      MGR2      18-JAN-2018    31-JAN-2018

例如,EMP1,EMP2,EMP3和EMP4在整个期间(即从2018年1月1日至2018年1月31日)向MGR1,MGR2,MGR3,MGR4报告。 但是对于EMP5和EMP6情况却有所不同。 EMP5在整个期间一直从一名经理转到另一名经理(从向MGR5报告的1月1日至10月10日,向MGR1报告的1月11日至15月1日,向MGR2报告的从1月16日至20月1日,从向MGR3报告了1月21日至1月25日,向MGR4报告了从1月26日至1月31日)。 鉴于EMP6在此期间报告了两名管理者(从2018年1月1日至2018年1月10日向MGR6报告,从2018年1月18日至2018年1月31日向MGR2报告)

所需的结果集: 现在,我想通过以下方式呈现数据集中包含的信息

**MGR_ID    FRM_DT         TO_DT          SUB_ORD_CNT    SUB_ORDINATES**
MGR1      01-JAN-2018    10-JAN-2018    1              EMP1
MGR1      11-JAN-2018    15-JAN-2018    2              EMP1,EMP5
MGR1      16-JAN-2018    31-JAN-2018    1              EMP1
MGR2      01-JAN-2018    15-JAN-2018    1              EMP2
MGR2      16-JAN-2018    17-JAN-2018    2              EMP2,EMP5
MGR2      18-JAN-2018    20-JAN-2018    3              EMP2,EMP5,EMP6
MGR2      21-JAN-2018    31-JAN-2018    2              EMP2,EMP6
MGR3      01-JAN-2018    20-JAN-2018    1              EMP3
MGR3      21-JAN-2018    25-JAN-2018    2              EMP3,EMP5
MGR3      26-JAN-2018    31-JAN-2018    1              EMP3
MGR4      01-JAN-2018    25-JAN-2018    1              EMP4
MGR4      26-JAN-2018    31-JAN-2018    2              EMP4,EMP5
MGR5      01-JAN-2018    10-JAN-2018    1              EMP5

也就是说,我想报告在特定时间段(从01-JAN-2018到2018年1月31日)向管理人员报告的员工人数(以及用逗号分隔的EMPID)。 例如,MGR2在2018年1月16日至2018年1月17日以及2018年1月18日至2018年1月20日期间监督了两名雇员(EMP2和EMP5)

>

我想知道,SQL怎么可能。我正在使用11g版本的ORACLE DB。 任何解决方案的线索都将受到高度赞赏。谢谢。

产生所需数据集的代码在下面给出:

create table emp_mgr_relation
(
 emp_id varchar2(30),
 mgr_id varchar2(30),
 frm_dt date,
 to_dt date
 );
 /
 insert into emp_mgr_relation values('EMP1','MGR1','01-JAN-2018','31-JAN-2018');
 insert into emp_mgr_relation values('EMP2','MGR2','01-JAN-2018','31-JAN-2018');
 insert into emp_mgr_relation values('EMP3','MGR3','01-JAN-2018','31-JAN-2018');
 insert into emp_mgr_relation values('EMP4','MGR4','01-JAN-2018','31-JAN-2018');
 insert into emp_mgr_relation values('EMP5','MGR5','01-JAN-2018','10-JAN-2018');
 insert into emp_mgr_relation values('EMP5','MGR1','11-JAN-2018','15-JAN-2018');
 insert into emp_mgr_relation values('EMP5','MGR2','16-JAN-2018','20-JAN-2018');
 insert into emp_mgr_relation values('EMP5','MGR3','21-JAN-2018','25-JAN-2018');
 insert into emp_mgr_relation values('EMP5','MGR4','26-JAN-2018','31-JAN-2018');
 insert into emp_mgr_relation values('EMP6','MGR6','01-JAN-2018','15-JAN-2018');
 insert into emp_mgr_relation values('EMP6','MGR2','18-JAN-2018','31-JAN-2018'); 

3 个答案:

答案 0 :(得分:1)

作为一种蛮力的方法,您可以使用分层查询或递归CTE将所有原始日期范围扩展为每位员工每天一行:

with rcte1 (emp_id, mgr_id, dt, to_dt) as (
  select emp_id, mgr_id, frm_dt, to_dt
  from emp_mgr_relation
  union all
  select emp_id, mgr_id, dt + 1, to_dt
  from rcte1
  where to_dt > dt
)
select emp_id, mgr_id, dt from rcte1 order by dt, emp_id, mgr_id;

EMP_ID                         MGR_ID                         DT        
------------------------------ ------------------------------ ----------
EMP1                           MGR1                           2018-01-01
EMP2                           MGR2                           2018-01-01
EMP3                           MGR3                           2018-01-01
EMP4                           MGR4                           2018-01-01
EMP5                           MGR5                           2018-01-01
EMP6                           MGR6                           2018-01-01
EMP1                           MGR1                           2018-01-02
EMP2                           MGR2                           2018-01-02
...
EMP6                           MGR2                           2018-01-30
EMP1                           MGR1                           2018-01-31
EMP2                           MGR2                           2018-01-31
EMP3                           MGR3                           2018-01-31
EMP4                           MGR4                           2018-01-31
EMP5                           MGR4                           2018-01-31
EMP6                           MGR2                           2018-01-31

184 rows selected. 

,然后按管理员和日期汇总:

with rcte1 (emp_id, mgr_id, dt, to_dt) as (
  select emp_id, mgr_id, frm_dt, to_dt
  from emp_mgr_relation
  union all
  select emp_id, mgr_id, dt + 1, to_dt
  from rcte1
  where to_dt > dt
),
cte2 (mgr_id, dt, sub_ord_cn, subordinates) as (
  select mgr_id, dt, count(*), listagg (emp_id,  ',') within group (order by emp_id)
  from rcte1
  group by mgr_id, dt
)
select * from cte2 order by mgr_id, dt;

MGR_ID                         DT         SUB_ORD_CN SUBORDINATES                  
------------------------------ ---------- ---------- ------------------------------
MGR1                           2018-01-01          1 EMP1                          
MGR1                           2018-01-02          1 EMP1                          
MGR1                           2018-01-03          1 EMP1                          
...
MGR1                           2018-01-10          1 EMP1                          
MGR1                           2018-01-11          2 EMP1,EMP5                     
MGR1                           2018-01-12          2 EMP1,EMP5                     
MGR1                           2018-01-13          2 EMP1,EMP5                     
...

149 rows selected. 

然后应用Tabibitosan

with rcte1 (emp_id, mgr_id, dt, to_dt) as (
  select emp_id, mgr_id, frm_dt, to_dt
  from emp_mgr_relation
  union all
  select emp_id, mgr_id, dt + 1, to_dt
  from rcte1
  where to_dt > dt
),
cte2 (mgr_id, dt, sub_ord_cn, subordinates) as (
  select mgr_id, dt, count(*), listagg (emp_id,  ',') within group (order by emp_id)    
  from rcte1
  group by mgr_id, dt
),
cte3 (mgr_id, dt, sub_ord_cn, subordinates, bucket) as (
  select mgr_id, dt, sub_ord_cn, subordinates,
    row_number() over (partition by mgr_id, sub_ord_cn, subordinates order by dt)
      - row_number() over (partition by mgr_id order by dt)
  from cte2
)
select * from cte3 order by mgr_id, dt;

MGR_ID                         DT         SUB_ORD_CN SUBORDINATES                       BUCKET
------------------------------ ---------- ---------- ------------------------------ ----------
MGR1                           2018-01-01          1 EMP1                                    0
MGR1                           2018-01-02          1 EMP1                                    0
...
MGR1                           2018-01-10          1 EMP1                                    0
MGR1                           2018-01-11          2 EMP1,EMP5                             -10
...
MGR1                           2018-01-15          2 EMP1,EMP5                             -10
MGR1                           2018-01-16          1 EMP1                                   -5
...
MGR6                           2018-01-15          1 EMP6                                    0

149 rows selected. 

,然后汇总这些经理存储桶:

with rcte1 (emp_id, mgr_id, dt, to_dt) as (
  select emp_id, mgr_id, frm_dt, to_dt
  from emp_mgr_relation
  union all
  select emp_id, mgr_id, dt + 1, to_dt
  from rcte1
  where to_dt > dt
),
cte2 (mgr_id, dt, sub_ord_cn, subordinates) as (
  select mgr_id, dt, count(*), listagg (emp_id,  ',') within group (order by emp_id)    
  from rcte1
  group by mgr_id, dt
),
cte3 (mgr_id, dt, sub_ord_cn, subordinates, bucket) as (
  select mgr_id, dt, sub_ord_cn, subordinates,
    row_number() over (partition by mgr_id, sub_ord_cn, subordinates order by dt)
      - row_number() over (partition by mgr_id order by dt)
  from cte2
)
select mgr_id, min(dt) as frm_dt, max(dt) as to_dt, sub_ord_cn, subordinates
from cte3
group by mgr_id, bucket, sub_ord_cn, subordinates
order by mgr_id, frm_dt;

得到:

MGR_ID                         FRM_DT     TO_DT      SUB_ORD_CN SUBORDINATES                  
------------------------------ ---------- ---------- ---------- ------------------------------
MGR1                           2018-01-01 2018-01-10          1 EMP1                          
MGR1                           2018-01-11 2018-01-15          2 EMP1,EMP5                     
MGR1                           2018-01-16 2018-01-31          1 EMP1                          
MGR2                           2018-01-01 2018-01-15          1 EMP2                          
MGR2                           2018-01-16 2018-01-17          2 EMP2,EMP5                     
MGR2                           2018-01-18 2018-01-20          3 EMP2,EMP5,EMP6                
MGR2                           2018-01-21 2018-01-31          2 EMP2,EMP6                     
MGR3                           2018-01-01 2018-01-20          1 EMP3                          
MGR3                           2018-01-21 2018-01-25          2 EMP3,EMP5                     
MGR3                           2018-01-26 2018-01-31          1 EMP3                          
MGR4                           2018-01-01 2018-01-25          1 EMP4                          
MGR4                           2018-01-26 2018-01-31          2 EMP4,EMP5                     
MGR5                           2018-01-01 2018-01-10          1 EMP5                          
MGR6                           2018-01-01 2018-01-15          1 EMP6                          

14 rows selected. 

如果您使用的版本具有递归CTE的错误(11.2.0.2似乎仅返回11行,可能是由于11840579(已在11.2.0.3中修复)),您可以改用分层查询。像这样:

with cte1 (emp_id, mgr_id, dt) as (
  select emp_id, mgr_id, frm_dt + level - 1
  from emp_mgr_relation
  connect by emp_id = prior emp_id
  and mgr_id = prior mgr_id
  and frm_dt = prior frm_dt
  and prior dbms_random.value is not null
  and level <= to_dt - frm_dt + 1  --correction here
),
cte2 (mgr_id, dt, sub_ord_cn, subordinates) as (
  select mgr_id, dt, count(*), listagg (emp_id,  ',') within group (order by emp_id)
  from cte1
  group by mgr_id, dt
),
cte3 (mgr_id, dt, sub_ord_cn, subordinates, bucket) as (
  select mgr_id, dt, sub_ord_cn, subordinates,
    row_number() over (partition by mgr_id, sub_ord_cn, subordinates order by dt)
      - row_number() over (partition by mgr_id order by dt)
  from cte2
)
select mgr_id, min(dt) as frm_dt, max(dt) as to_dt, sub_ord_cn, subordinates
from cte3
group by mgr_id, bucket, sub_ord_cn, subordinates
order by mgr_id, frm_dt;

获得相同的结果。

答案 1 :(得分:1)

您可以通过将视图从员工日期范围切换到经理范围来解决此问题。即,对于每位经理,获取其报告更改的时间段。

为此,首先将员工的日期从/转换为日期的单个列:

with dates as (
  select * from emp_mgr_relation
  unpivot (
    dt for ( src ) in ( frm_dt, to_dt )
  )
)
  select * from dates
  order  by mgr_id, dt;

EMP_ID   MGR_ID   SRC      DT            
EMP1     MGR1     FRM_DT   01-JAN-2018   
EMP5     MGR1     FRM_DT   11-JAN-2018   
EMP5     MGR1     TO_DT    15-JAN-2018   
EMP1     MGR1     TO_DT    31-JAN-2018   
EMP2     MGR2     FRM_DT   01-JAN-2018   
EMP5     MGR2     FRM_DT   16-JAN-2018   
EMP6     MGR2     FRM_DT   18-JAN-2018
...

您现在需要将其设置为开始/结束范围。您可以使用lead()来获得经理的下一个日期:

with dates as (
  select * from emp_mgr_relation
  unpivot (
    dt for ( src ) in ( frm_dt, to_dt )
  )
), ranges as (
  select emp_id, mgr_id, dt, dt st_dt, src, 
         lead ( dt ) over ( partition by mgr_id order by dt )  en_dt
  from   dates
)
  select * from ranges
  order  by mgr_id, dt;

EMP_ID   MGR_ID   DT            ST_DT         SRC      EN_DT         
EMP1     MGR1     01-JAN-2018   01-JAN-2018   FRM_DT   11-JAN-2018   
EMP5     MGR1     11-JAN-2018   11-JAN-2018   FRM_DT   15-JAN-2018   
EMP5     MGR1     15-JAN-2018   15-JAN-2018   TO_DT    31-JAN-2018   
EMP1     MGR1     31-JAN-2018   31-JAN-2018   TO_DT    <null>        
EMP2     MGR2     01-JAN-2018   01-JAN-2018   FRM_DT   16-JAN-2018   
EMP5     MGR2     16-JAN-2018   16-JAN-2018   FRM_DT   18-JAN-2018   
EMP6     MGR2     18-JAN-2018   18-JAN-2018   FRM_DT   20-JAN-2018 
...

然后加入该时段内向经理报告的员工的行:

with dates as (
  select * from emp_mgr_relation
  unpivot (
    dt for ( src ) in ( frm_dt, to_dt )
  )
), ranges as (
  select emp_id, mgr_id, dt, dt st_dt, src, 
         lead ( dt ) over ( partition by mgr_id order by dt )  en_dt
  from   dates
)
  select e.mgr_id, src, 
         case 
           when src = 'TO_DT' then st_dt + 1
           else st_dt
         end st_dt,
         case
           when src = 'TO_DT' or 
                lead ( src ) over ( 
                  partition by e.mgr_id order by st_dt 
                ) = 'TO_DT' or
                en_dt =  max ( en_dt ) over ( 
                           partition by e.mgr_id 
                         ) 
           then en_dt
           else
             en_dt - 1
         end en_dt,
         count(*) sub_ord_cn, 
         listagg ( e.emp_id, ',' ) 
           within group ( order by e.emp_id ) subordinates
  from   ranges r
  join   emp_mgr_relation e
  on     r.mgr_id = e.mgr_id
  and    e.frm_dt <= st_dt
  and    e.to_dt >= en_dt
  and    st_dt < en_dt
  group  by e.mgr_id, st_dt, en_dt, src
  order  by e.mgr_id, st_dt, en_dt;

MGR_ID   SRC      ST_DT         EN_DT         SUB_ORD_CN   SUBORDINATES     
MGR1     FRM_DT   01-JAN-2018   10-JAN-2018              1 EMP1             
MGR1     FRM_DT   11-JAN-2018   15-JAN-2018              2 EMP1,EMP5        
MGR1     TO_DT    16-JAN-2018   31-JAN-2018              1 EMP1             
MGR2     FRM_DT   01-JAN-2018   15-JAN-2018              1 EMP2             
MGR2     FRM_DT   16-JAN-2018   17-JAN-2018              2 EMP2,EMP5        
MGR2     FRM_DT   18-JAN-2018   20-JAN-2018              3 EMP2,EMP5,EMP6   
MGR2     TO_DT    21-JAN-2018   31-JAN-2018              2 EMP2,EMP6        
MGR3     FRM_DT   01-JAN-2018   20-JAN-2018              1 EMP3             
MGR3     FRM_DT   21-JAN-2018   25-JAN-2018              2 EMP3,EMP5        
MGR3     TO_DT    26-JAN-2018   31-JAN-2018              1 EMP3             
MGR4     FRM_DT   01-JAN-2018   25-JAN-2018              1 EMP4             
MGR4     FRM_DT   26-JAN-2018   31-JAN-2018              2 EMP4,EMP5        
MGR5     FRM_DT   01-JAN-2018   10-JAN-2018              1 EMP5             
MGR6     FRM_DT   01-JAN-2018   15-JAN-2018              1 EMP6 

请注意,由于您将结束日期报告为下一个开始日期的前一天,因此存在一些日期混淆。

虽然这又回到emp_mgr_relation,但您不需要生成行/员工/天。无论如何,在许多情况下,这将是您需要在输出中使用的期间/管理器数。

因此,与蛮力递归方法相比,它可能处理更少的rwos。所以要更快。

答案 2 :(得分:0)

只是想出了另一种解决问题的方法。这种方法不会在运行时生成行,因此可以解决早期解决方案的注释中讨论的问题。

select mgr_id,
       final_slice_from dt_frm,
       final_slice_to dt_to,
       regexp_count(emps, ',') + 1 sub_ord_cnt,
       emps sub_ordinates
  from (select mgr_id,
               final_slice_from,
               final_slice_to,
               (select listagg(emp_id, ',') within group(order by emp_id)
                  from emp_mgr_relation y
                 where y.mgr_id = r.mgr_id
                   and (final_slice_from between y.frm_dt and y.to_dt or
                       final_slice_to between y.frm_dt and y.to_dt)) emps
          from (select mgr_id,
                       slice_from + frm_dt_adj final_slice_from,
                       slice_to + to_dt_adj final_slice_to
                  from (select mgr_id,
                               slice_from,
                               slice_to,
                               frm_dt_flg,
                               to_dt_flg,
                               decode(nvl(frm_dt_flg, '#'), '#', 1, 0) frm_dt_adj,
                               decode(nvl(to_dt_flg, '#'), '#', -1, 0) to_dt_adj
                          from (select a.mgr_id,
                                       a.slice_from,
                                       a.slice_to,
                                       (select 'Y'
                                          from dual
                                         where exists
                                         (select 1
                                                  from emp_mgr_relation e
                                                 where a.mgr_id = e.mgr_id
                                                   and a.slice_from = e.frm_dt)) frm_dt_flg,
                                       (select 'Y'
                                          from dual
                                         where exists
                                         (select 1
                                                  from emp_mgr_relation d
                                                 where a.mgr_id = d.mgr_id
                                                   and a.slice_to = d.to_dt)) to_dt_flg
                                  from (
                                        --create time slice using lead function
                                        select mgr_id,
                                                dt slice_from,
                                                lead(dt, 1) over(partition by mgr_id order by dt) slice_to
                                          from (
                                                 --list all distinct dates manager wise 
                                                 select distinct mgr_id, frm_dt dt
                                                   from emp_mgr_relation
                                                 union
                                                 select distinct mgr_id, to_dt
                                                   from emp_mgr_relation)) a
                                 where slice_to is not null))) r)

查询结果:

MGR_ID  DT_FRM      DT_TO   SUB_ORD_CNT SUB_ORDINATES
MGR1    1/1/2018    1/10/2018   1       EMP1
MGR1    1/11/2018   1/15/2018   2       EMP1,EMP5
MGR1    1/16/2018   1/31/2018   1       EMP1
MGR2    1/1/2018    1/15/2018   1       EMP2
MGR2    1/16/2018   1/17/2018   2       EMP2,EMP5
MGR2    1/18/2018   1/20/2018   3       EMP2,EMP5,EMP6
MGR2    1/21/2018   1/31/2018   2       EMP2,EMP6
MGR3    1/1/2018    1/20/2018   1       EMP3
MGR3    1/21/2018   1/25/2018   2       EMP3,EMP5
MGR3    1/26/2018   1/31/2018   1       EMP3
MGR4    1/1/2018    1/25/2018   1       EMP4
MGR4    1/26/2018   1/31/2018   2       EMP4,EMP5
MGR5    1/1/2018    1/10/2018   1       EMP5
MGR6    1/1/2018    1/15/2018   1       EMP6