查找在两个日期之间覆盖的员工的更有效方法
More efficient way to find employees with coverage between two dates
我需要检索一份员工列表,并为每位员工列出他们在给定年份积极参与福利保险的月份列表。有一个包含工作数据的 table 和一个包含福利信息的 table。还有一个交付日期 table,它列出了 2007 年至 2018 年的每个日期,并且对于每个日期,它显示了月中的第几天、一年中的月份和日历年。
我现在编写查询的方式是说:找到日期 table 中 1) 在提示年份的 01/01 和 12/31(或当前日期)之间的所有日期, 以较早者为准), 2) 在员工享受福利期间table。对于每个日期,我还想要来自工作 table 的 deptid 和来自该日期的福利 table 的福利计划。然后我做一个不同的,只显示每个员工的月份和日历年。
这行得通,但是当我尝试为其中有很多人的部门这样做时,问题就来了。 运行 需要很长时间,我相信是因为它为每个员工检索多达 365 行,然后只显示其中的 12 行,因为它只拖了不同的月份。我觉得有更好的方法可以做到这一点,我只是想不出它是什么。
以下是我正在使用的 tables 的一些简化示例:
日期Table
THE_DATE MONTHOFYEAR CALENDAR_YEAR
01-OCT-15 10 2015
02-OCT-15 10 2015
03-OCT-15 10 2015
...
职位Table
(A=活跃;I=不活跃)
EMPLID EFFDT DEPTID HR_STATUS
00123 01-FEB-15 900 A
00123 30-JUN-15 900 I
00123 01-AUG-15 901 A
好处Table
EMPLID EFFDT BENEFIT_PLAN STATUS
00123 01-MAR-15 PPO A
00123 31-JUL-15 I
00123 01-SEP-15 HMO A
想要的结果
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
00123 2015 3 900 PPO
00123 2015 4 900 PPO
00123 2015 5 900 PPO
00123 2015 6 900 PPO
00123 2015 7 900 PPO
00123 2015 9 901 HMO
00123 2015 10 901 HMO
00123 2015 11 901 HMO
^ (shows November row even though employee was only covered for part of this month)
示例SQL 以获得上述结果
SELECT DISTINCT J.EMPLID, D.CALENDAR_YEAR, D.MONTHOFYEAR, J.DEPTID, B.BENEFIT_PLAN
FROM DATES D,
JOBS J
JOIN
BENEFITS B
ON J.EMPLID = B.EMPLID
WHERE D.THE_DATE <= SYSDATE
AND D.THE_DATE BETWEEN
TO_DATE(:YEAR_PROMPT || '01-01', 'YYYY-MM-DD')
AND
TO_DATE(:YEAR_PROMPT || '12-31', 'YYYY-MM-DD')
AND B.STATUS = 'A'
AND D.THE_DATE BETWEEN
B.EFFDT
AND
NVL(SELECT MIN(B_ED.EFFDT)
FROM BENEFITS B_ED
WHERE B_ED.EMPLID = B.EMPLID
AND B_ED.EFFDT > B.EFFDT
, SYSDATE)
AND J.EFFDT = (SELECT MAX(J_ED.EFFDT)
FROM JOBS J_ED
WHERE J_ED.EMPLID = J.EMPLID
AND J_ED.EFFDT <= D.THE_DATE)
我可以不说 "retrieve every single date and check to see if it fits the criteria" 而是以某种方式更改逻辑以获得相同的结果而无需翻阅这么多行吗?
是;使用LEAD()
解析函数,可以计算jobs和benefits表中的下一个effdt,方便区间查询。
类似于:
with dates as (select trunc(sysdate, 'yyyy') - 1 + level the_date,
to_number(to_char(trunc(sysdate, 'yyyy') - 1 + level, 'mm')) monthofyear,
to_number(to_char(sysdate, 'yyyy')) calendar_year
from dual
connect by level <= 365),
jobs as (select 123 emplid, to_date('01/02/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('30/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'I' hr_status from dual union all
select 123 emplid, to_date('01/08/2015', 'dd/mm/yyyy') effdt, 901 deptid, 'A' hr_status from dual),
benefits as (select 123 emplid, to_date('01/03/2015', 'dd/mm/yyyy') effdt, 'PPO' benefit_plan, 'A' status from dual union all
select 123 emplid, to_date('31/07/2015', 'dd/mm/yyyy') effdt, null benefit_plan, 'I' status from dual union all
select 123 emplid, to_date('01/09/2015', 'dd/mm/yyyy') effdt, 'HMO' benefit_plan, 'A' status from dual),
-- ********* end of mimicking your tables ********* --
j as (select emplid,
effdt,
deptid,
hr_status,
lead(effdt, 1, sysdate) over (partition by emplid order by effdt) next_effdt
from jobs),
b as (select emplid,
effdt,
benefit_plan,
status,
lead(effdt, 1, sysdate) over (partition by emplid order by effdt) next_effdt
from benefits)
select distinct j.emplid,
d.calendar_year,
d.monthofyear,
j.deptid,
b.benefit_plan
from j
inner join dates d on (d.the_date >= j.effdt and d.the_date < j.next_effdt)
inner join b on (j.emplid = b.emplid)
where d.the_date <= sysdate
and d.the_date between to_date (:year_prompt || '01-01', 'YYYY-MM-DD')
and to_date (:year_prompt || '12-31', 'YYYY-MM-DD') -- if no index on d.the_date, maybe use trunc(the_date, 'yyyy') = :year_prompt
and b.status = 'A'
and d.the_date between b.effdt and b.next_effdt
order by 1, 4, 2, 3;
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
---------- ------------- ----------- ---------- ------------
123 2015 3 900 PPO
123 2015 4 900 PPO
123 2015 5 900 PPO
123 2015 6 900 PPO
123 2015 7 900 PPO
123 2015 9 901 HMO
123 2015 10 901 HMO
123 2015 11 901 HMO
(显然,您可以从上述查询中排除 dates
、jobs
和 benefits
子查询,因为您已经有了这些表。它们只存在于查询中模拟其中包含该数据的表,而无需实际创建表。)。
ETA:这是一个仅根据传入的年份计算 12 个月的版本,它将日期行减少到 12 行,而不是 365/366 行。
不幸的是,当您有多个行从同一个月开始时,您仍然需要不同的,以考虑到。
例如,对于下例中的数据,如果您删除了不同的数据,第 6 个月的结果将是 3 行。但是,distinct 操作的行数将比以前少得多。
with dates as (select add_months(to_date(:year_prompt || '-01-01', 'YYYY-MM-DD'), - 1 + level) the_date,
level monthofyear,
:year_prompt calendar_year -- assuming this is a number
from dual
connect by level <= 12),
jobs as (select 123 emplid, to_date('01/02/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('15/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'I' hr_status from dual union all
select 123 emplid, to_date('26/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('01/08/2015', 'dd/mm/yyyy') effdt, 901 deptid, 'A' hr_status from dual),
benefits as (select 123 emplid, to_date('01/03/2015', 'dd/mm/yyyy') effdt, 'PPO' benefit_plan, 'A' status from dual union all
select 123 emplid, to_date('31/07/2015', 'dd/mm/yyyy') effdt, null benefit_plan, 'I' status from dual union all
select 123 emplid, to_date('01/09/2015', 'dd/mm/yyyy') effdt, 'HMO' benefit_plan, 'A' status from dual),
-- ********* end of mimicking your tables ********* --
j as (select emplid,
trunc(effdt, 'mm') effdt,
deptid,
hr_status,
trunc(coalesce(lead(effdt) over (partition by emplid order by effdt) -1, sysdate), 'mm') end_effdt
-- subtracting 1 from the lead(effdt) since here since the original sql had d.the_date < j.next_effdt and we need
-- to take into account when the next_effdt is the first of the month; we want the previous month to be displayed
from jobs),
b as (select emplid,
trunc(effdt, 'mm') effdt,
benefit_plan,
status,
trunc(lead(effdt, 1, sysdate) over (partition by emplid order by effdt), 'mm') end_effdt
from benefits)
select distinct j.emplid,
d.calendar_year,
d.monthofyear,
j.deptid,
b.benefit_plan
from j
inner join dates d on (d.the_date between j.effdt and j.end_effdt)
inner join b on (j.emplid = b.emplid)
where d.the_date <= sysdate
and b.status = 'A'
and d.the_date between b.effdt and b.end_effdt
order by 1, 4, 2, 3;
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
---------- ------------- ----------- ---------- --------------------------------
123 2015 3 900 PPO
123 2015 4 900 PPO
123 2015 5 900 PPO
123 2015 6 900 PPO
123 2015 6 900 PPO
123 2015 7 900 PPO
123 2015 9 901 HMO
123 2015 10 901 HMO
123 2015 11 901 HMO
我需要检索一份员工列表,并为每位员工列出他们在给定年份积极参与福利保险的月份列表。有一个包含工作数据的 table 和一个包含福利信息的 table。还有一个交付日期 table,它列出了 2007 年至 2018 年的每个日期,并且对于每个日期,它显示了月中的第几天、一年中的月份和日历年。
我现在编写查询的方式是说:找到日期 table 中 1) 在提示年份的 01/01 和 12/31(或当前日期)之间的所有日期, 以较早者为准), 2) 在员工享受福利期间table。对于每个日期,我还想要来自工作 table 的 deptid 和来自该日期的福利 table 的福利计划。然后我做一个不同的,只显示每个员工的月份和日历年。
这行得通,但是当我尝试为其中有很多人的部门这样做时,问题就来了。 运行 需要很长时间,我相信是因为它为每个员工检索多达 365 行,然后只显示其中的 12 行,因为它只拖了不同的月份。我觉得有更好的方法可以做到这一点,我只是想不出它是什么。
以下是我正在使用的 tables 的一些简化示例:
日期Table
THE_DATE MONTHOFYEAR CALENDAR_YEAR
01-OCT-15 10 2015
02-OCT-15 10 2015
03-OCT-15 10 2015
...
职位Table
(A=活跃;I=不活跃)
EMPLID EFFDT DEPTID HR_STATUS
00123 01-FEB-15 900 A
00123 30-JUN-15 900 I
00123 01-AUG-15 901 A
好处Table
EMPLID EFFDT BENEFIT_PLAN STATUS
00123 01-MAR-15 PPO A
00123 31-JUL-15 I
00123 01-SEP-15 HMO A
想要的结果
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
00123 2015 3 900 PPO
00123 2015 4 900 PPO
00123 2015 5 900 PPO
00123 2015 6 900 PPO
00123 2015 7 900 PPO
00123 2015 9 901 HMO
00123 2015 10 901 HMO
00123 2015 11 901 HMO
^ (shows November row even though employee was only covered for part of this month)
示例SQL 以获得上述结果
SELECT DISTINCT J.EMPLID, D.CALENDAR_YEAR, D.MONTHOFYEAR, J.DEPTID, B.BENEFIT_PLAN
FROM DATES D,
JOBS J
JOIN
BENEFITS B
ON J.EMPLID = B.EMPLID
WHERE D.THE_DATE <= SYSDATE
AND D.THE_DATE BETWEEN
TO_DATE(:YEAR_PROMPT || '01-01', 'YYYY-MM-DD')
AND
TO_DATE(:YEAR_PROMPT || '12-31', 'YYYY-MM-DD')
AND B.STATUS = 'A'
AND D.THE_DATE BETWEEN
B.EFFDT
AND
NVL(SELECT MIN(B_ED.EFFDT)
FROM BENEFITS B_ED
WHERE B_ED.EMPLID = B.EMPLID
AND B_ED.EFFDT > B.EFFDT
, SYSDATE)
AND J.EFFDT = (SELECT MAX(J_ED.EFFDT)
FROM JOBS J_ED
WHERE J_ED.EMPLID = J.EMPLID
AND J_ED.EFFDT <= D.THE_DATE)
我可以不说 "retrieve every single date and check to see if it fits the criteria" 而是以某种方式更改逻辑以获得相同的结果而无需翻阅这么多行吗?
是;使用LEAD()
解析函数,可以计算jobs和benefits表中的下一个effdt,方便区间查询。
类似于:
with dates as (select trunc(sysdate, 'yyyy') - 1 + level the_date,
to_number(to_char(trunc(sysdate, 'yyyy') - 1 + level, 'mm')) monthofyear,
to_number(to_char(sysdate, 'yyyy')) calendar_year
from dual
connect by level <= 365),
jobs as (select 123 emplid, to_date('01/02/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('30/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'I' hr_status from dual union all
select 123 emplid, to_date('01/08/2015', 'dd/mm/yyyy') effdt, 901 deptid, 'A' hr_status from dual),
benefits as (select 123 emplid, to_date('01/03/2015', 'dd/mm/yyyy') effdt, 'PPO' benefit_plan, 'A' status from dual union all
select 123 emplid, to_date('31/07/2015', 'dd/mm/yyyy') effdt, null benefit_plan, 'I' status from dual union all
select 123 emplid, to_date('01/09/2015', 'dd/mm/yyyy') effdt, 'HMO' benefit_plan, 'A' status from dual),
-- ********* end of mimicking your tables ********* --
j as (select emplid,
effdt,
deptid,
hr_status,
lead(effdt, 1, sysdate) over (partition by emplid order by effdt) next_effdt
from jobs),
b as (select emplid,
effdt,
benefit_plan,
status,
lead(effdt, 1, sysdate) over (partition by emplid order by effdt) next_effdt
from benefits)
select distinct j.emplid,
d.calendar_year,
d.monthofyear,
j.deptid,
b.benefit_plan
from j
inner join dates d on (d.the_date >= j.effdt and d.the_date < j.next_effdt)
inner join b on (j.emplid = b.emplid)
where d.the_date <= sysdate
and d.the_date between to_date (:year_prompt || '01-01', 'YYYY-MM-DD')
and to_date (:year_prompt || '12-31', 'YYYY-MM-DD') -- if no index on d.the_date, maybe use trunc(the_date, 'yyyy') = :year_prompt
and b.status = 'A'
and d.the_date between b.effdt and b.next_effdt
order by 1, 4, 2, 3;
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
---------- ------------- ----------- ---------- ------------
123 2015 3 900 PPO
123 2015 4 900 PPO
123 2015 5 900 PPO
123 2015 6 900 PPO
123 2015 7 900 PPO
123 2015 9 901 HMO
123 2015 10 901 HMO
123 2015 11 901 HMO
(显然,您可以从上述查询中排除 dates
、jobs
和 benefits
子查询,因为您已经有了这些表。它们只存在于查询中模拟其中包含该数据的表,而无需实际创建表。)。
ETA:这是一个仅根据传入的年份计算 12 个月的版本,它将日期行减少到 12 行,而不是 365/366 行。
不幸的是,当您有多个行从同一个月开始时,您仍然需要不同的,以考虑到。
例如,对于下例中的数据,如果您删除了不同的数据,第 6 个月的结果将是 3 行。但是,distinct 操作的行数将比以前少得多。
with dates as (select add_months(to_date(:year_prompt || '-01-01', 'YYYY-MM-DD'), - 1 + level) the_date,
level monthofyear,
:year_prompt calendar_year -- assuming this is a number
from dual
connect by level <= 12),
jobs as (select 123 emplid, to_date('01/02/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('15/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'I' hr_status from dual union all
select 123 emplid, to_date('26/06/2015', 'dd/mm/yyyy') effdt, 900 deptid, 'A' hr_status from dual union all
select 123 emplid, to_date('01/08/2015', 'dd/mm/yyyy') effdt, 901 deptid, 'A' hr_status from dual),
benefits as (select 123 emplid, to_date('01/03/2015', 'dd/mm/yyyy') effdt, 'PPO' benefit_plan, 'A' status from dual union all
select 123 emplid, to_date('31/07/2015', 'dd/mm/yyyy') effdt, null benefit_plan, 'I' status from dual union all
select 123 emplid, to_date('01/09/2015', 'dd/mm/yyyy') effdt, 'HMO' benefit_plan, 'A' status from dual),
-- ********* end of mimicking your tables ********* --
j as (select emplid,
trunc(effdt, 'mm') effdt,
deptid,
hr_status,
trunc(coalesce(lead(effdt) over (partition by emplid order by effdt) -1, sysdate), 'mm') end_effdt
-- subtracting 1 from the lead(effdt) since here since the original sql had d.the_date < j.next_effdt and we need
-- to take into account when the next_effdt is the first of the month; we want the previous month to be displayed
from jobs),
b as (select emplid,
trunc(effdt, 'mm') effdt,
benefit_plan,
status,
trunc(lead(effdt, 1, sysdate) over (partition by emplid order by effdt), 'mm') end_effdt
from benefits)
select distinct j.emplid,
d.calendar_year,
d.monthofyear,
j.deptid,
b.benefit_plan
from j
inner join dates d on (d.the_date between j.effdt and j.end_effdt)
inner join b on (j.emplid = b.emplid)
where d.the_date <= sysdate
and b.status = 'A'
and d.the_date between b.effdt and b.end_effdt
order by 1, 4, 2, 3;
EMPLID CALENDAR_YEAR MONTHOFYEAR DEPTID BENEFIT_PLAN
---------- ------------- ----------- ---------- --------------------------------
123 2015 3 900 PPO
123 2015 4 900 PPO
123 2015 5 900 PPO
123 2015 6 900 PPO
123 2015 6 900 PPO
123 2015 7 900 PPO
123 2015 9 901 HMO
123 2015 10 901 HMO
123 2015 11 901 HMO