我有一个如下所示的输入数据集,其中包含雇员与他的经理在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');
答案 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