Отношения, зависящие от времени между Сотрудником и его Менеджером - PullRequest
0 голосов
/ 30 октября 2018

У меня есть набор входных данных, указанный ниже, который содержит отношения между Сотрудником и его Менеджером за период с 01 января 2018 года по 31 января 2018 года.

НАБОР ДАННЫХ ВВОДА:

**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 отчитываются перед MGR1, MGR2, MGR3, MGR4 за весь период, то есть с 01 января 2018 года по 31 января 2018 года. Но для EMP5 и EMP6 ситуация другая. EMP5 продолжал переключаться с одного менеджера на другого в течение всего периода (с 01 января по 10 января, сообщенного MGR5, с 11 января по 15 января, сообщенного MGR1, с 16 января по 20 января, сообщенного MGR2, из С 21 января по 25 января сообщалось MGR3, с 26 января по 31 января сообщалось MGR4). Принимая во внимание, что EMP6 сообщил о двух менеджерах за отчетный период (с 01 января 2018 года по 10 января 2018 года перед MGR6, с 18 января 2018 года по 31 января 2018 года перед 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

То есть я хочу сообщить, сколько сотрудников (вместе со своими EMPID, разделенными запятыми) сообщили руководителю во время определенного временного интервала за период с 01 января 2018 года по 31 января 2018 года. Например, MGR2 наблюдает за двумя сотрудниками (EMP2 и EMP5) в период с 16 января 2018 года по 17 января 2018 года и в период с 18 января 2018 года по 20 января 2018 года MGR2 наблюдает за тремя сотрудниками (EMP2, EMP5 и EMP6)

Мне интересно, как это возможно с 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 голосов
/ 02 ноября 2018

Просто разобрался с другим подходом к решению проблемы. Этот подход не генерирует строки во время выполнения, поэтому учитывает проблему, обсуждаемую в комментариях к более раннему решению.

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
0 голосов
/ 09 ноября 2018

Это можно решить, переключив представление из диапазонов дат сотрудников в диапазоны менеджеров. то есть для каждого менеджера получите периоды, когда их отчеты изменились.

Для этого сначала нужно преобразовать сотрудника из / в даты в один столбец дат:

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, вам не нужно генерировать строку / employee / day. Который во многих случаях будет тем количеством периодов / менеджера, которое вам нужно в любом случае.

Таким образом, он может обработать меньше rwo, чем рекурсивный метод грубой силы. И так будь быстрее.

0 голосов
/ 30 октября 2018

В качестве довольно грубого подхода вы можете использовать иерархический запрос или рекурсивный 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. 

и затем применить Табибитозан :

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;

, который получает тот же результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...