SQL计算无工作天数
Posted
技术标签:
【中文标题】SQL计算无工作天数【英文标题】:SQL calculate no job days 【发布时间】:2019-10-03 11:55:38 【问题描述】:我必须编写一个 oracle SQL 查询来计算给定月份的总“无工作天数”和来自 LOG 表的工作 ID。表的数据如下:
LogId JobID LogDate JobStatu
1 1 09/01/2019 active
2 1 09/02/2019 end
3 2 08/03/2019 active
4 2 08/05/2019 suspended
5 2 08/08/2019 active
6 2 08/15/2019 end
job-days 是活动和结束或活动和暂停之间的天数。作业可能会在两者之间暂停。 所以在上面的例子中,9 月份的工作日是 1(从 1 日开始,2 日结束),8 月份是 9 天(从 3 日开始,5 日暂停,所以总共 2 天的工作,然后再次开始8 号到 15 号结束是 7 天,所以总工作天数是 2 + 7 = 9)。
预期结果,9 月 29 日(第 30 个月的总工作天数 - 1 天)和 8 月 22 日(31 - 9)的无工作天数。
谁能帮我为此编写一个 Oracle SQL 查询。
谢谢。
【问题讨论】:
【参考方案1】:你可以试试SQL for Pattern Matching (MATCH_RECOGNIZE):
WITH t(LogId, JobID, LogDate, JobStatus) AS
(SELECT 1, 1, TO_DATE('09/01/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 2, 1, TO_DATE('09/02/2019','mm/dd/YYYY'), 'end' FROM dual UNION ALL
SELECT 3, 2, TO_DATE('08/03/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 4, 2, TO_DATE('08/05/2019','mm/dd/YYYY'), 'suspended' FROM dual UNION ALL
SELECT 5, 2, TO_DATE('08/08/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 6, 2, TO_DATE('08/15/2019','mm/dd/YYYY'), 'end' FROM dual UNION ALL
SELECT 7, 3, TO_DATE('06/01/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 8, 3, TO_DATE('06/04/2019','mm/dd/YYYY'), 'suspended' FROM dual UNION ALL
SELECT 9, 3, TO_DATE('06/08/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 10, 3, TO_DATE('06/12/2019','mm/dd/YYYY'), 'suspended' FROM dual UNION ALL
SELECT 11, 3, TO_DATE('06/18/2019','mm/dd/YYYY'), 'active' FROM dual UNION ALL
SELECT 12, 3, TO_DATE('06/25/2019','mm/dd/YYYY'), 'end' FROM dual),
m as
(SELECT JobID, ACTIVE_DATE, END_DATE, SUSPEND_DAYS, JobStatus, var_match
FROM t
MATCH_RECOGNIZE (
PARTITION BY JobID
ORDER BY LogId
MEASURES
CLASSIFIER() AS var_match,
FINAL FIRST(s_ACTIVE.LogDate) AS ACTIVE_DATE,
FINAL LAST(s_END.LogDate) AS END_DATE,
(s_REACTIVE.LogDate - s_SUSPENDED.LogDate) AS SUSPEND_DAYS
ALL ROWS PER MATCH
PATTERN ( s_ACTIVE (s_SUSPENDED s_REACTIVE)* s_END )
DEFINE
s_ACTIVE AS JobStatus = 'active',
s_REACTIVE AS JobStatus = 'active',
s_END AS JobStatus = 'end',
s_SUSPENDED AS JobStatus = 'suspended'
)
)
SELECT JobID,
MIN(ACTIVE_DATE) AS START_DATE,
MAX(END_DATE) AS END_DATE,
SUM(SUSPEND_DAYS) AS SUSPENDED_DAYS,
MAX(END_DATE) - MIN(ACTIVE_DATE) - NVL(SUM(SUSPEND_DAYS),0) AS ACTIVE_DAYS,
EXTRACT(DAY FROM (LAST_DAY(MIN(ACTIVE_DATE)))) - (MAX(END_DATE) - MIN(ACTIVE_DATE) - NVL(SUM(SUSPEND_DAYS),0)) AS ACTIVE_DAYS_FROM_CURRENT_MONTH
FROM m
WHERE JobStatus = 'active'
GROUP BY JobID
ORDER BY JobID;
结果:
+-------------------------------------------------------------------------------------+
|JOBID|START_DATE|END_DATE |SUSPENDED_DAYS|ACTIVE_DAYS|ACTIVE_DAYS_FROM_CURRENT_MONTH|
+-------------------------------------------------------------------------------------+
|1 |01.09.2019|02.09.2019| |1 |29 |
|2 |03.08.2019|15.08.2019|3 |9 |22 |
|3 |01.06.2019|25.06.2019|10 |14 |16 |
+-------------------------------------------------------------------------------------+
【讨论】:
【参考方案2】:您可以在下面尝试 - 只需将 datediff()
更改为等效的 oracle 函数
DEMO
select jobid,SUM(logdate-t) as totaldays from
(
select t1.*,lag(logdate) over(partition by jobid order by logid) as t,
lag(jobstatus) over(partition by jobid order by logid) as st
from t1
)A where st='active' or (jobstatus='active' and (st is not null and st<>'suspended'))
group by jobid
注意:此演示在 sql server 中,但此概念适用于您的情况,您需要更改 datediff() 部分
【讨论】:
如果您有多个暂停期,它是否有效(请参阅我的回答中的数据)? 是的。我只是稍微修改了一下以符合我的要求。我没有使用滞后,而是使用了领先功能并仅检查原始表中的“活动”状态。【参考方案3】:您可以在 Oracle 中使用此代码。
在WITH AS
中,我提供了您的测试数据 (log_table
) 和日期表 (dates
)。
如果您的数据库中还没有已创建的数据库,您可以将该部分保存在日期表中:
with log_table (LogId, JobID, LogDate, JobStatu) as (
select 1, 1, to_date('09/01/2019', 'MM/DD/YYYY'), 'active' from dual
union all select 2, 1, to_date('09/02/2019', 'MM/DD/YYYY'), 'end' from dual
union all select 3, 2, to_date('08/03/2019', 'MM/DD/YYYY'), 'active' from dual
union all select 4, 2, to_date('08/05/2019', 'MM/DD/YYYY'), 'suspended' from dual
union all select 5, 2, to_date('08/08/2019', 'MM/DD/YYYY'), 'active' from dual
union all select 6, 2, to_date('08/15/2019', 'MM/DD/YYYY'), 'end' from dual
),
dates as (
select trunc(to_date('31.12.2019', 'DD.MM.YYYY') - (rownum - 1)) as date_val
, extract(month from trunc (to_date('31.12.2019', 'DD.MM.YYYY') - (rownum - 1))) as month_val
from dual connect by rownum < 366
)
select dts.month_val as "Month"
, count(dts.date_val) as "Number of days"
from dates dts
where not exists (select 1
from log_table lt1
where lt1.JobStatu = 'active'
and dts.date_val between lt1.LogDate
and (select min(LogDate) - 1
from log_table
where JobID = lt1.JobID
and JobStatu in ('end', 'suspended')
and LogDate > lt1.LogDate)
and rownum = 1)
group by dts.month_val
order by dts.month_val asc
结果:
+--------+----------------+
| Month | Number of days |
+--------+----------------+
| 1 | 31 |
| 2 | 28 |
| 3 | 31 |
| 4 | 30 |
| 5 | 31 |
| 6 | 30 |
| 7 | 31 |
| 8 | 22 |
| 9 | 29 |
| 10 | 31 |
| 11 | 30 |
| 12 | 31 |
+--------+----------------+
【讨论】:
【参考方案4】:我正在尝试将 @Fahmi 答案转换为 Oracle,但出现错误:
with Log_data as
(
select tkt_logtest_kb.*, lag(logdate) over(partition by jobid order by logid) as t,
lag(jobstatus) over(partition by jobid order by logid) as st
from tkt_logtest_kb)
select Log_data.jobid, sum(to_date(t, 'MM-dd-yyyy') - to_date(logdate, 'MM-dd-yyyy')) as totaldays from Log_data
where st='Active' or (jobstatus='Active' and (st is not null and st<>'Suspended'))
group by Log_data.jobid
ORA-01858:在需要数字的地方发现了一个非数字字符 01858. 00000 - “在需要数字的地方发现了一个非数字字符” *原因:要使用日期格式模型转换的输入数据是 不正确。输入数据不包含数字 格式模型所要求的。 *行动:修复输入数据或日期格式模型,以确保 元素在数量和类型上匹配。然后重试操作。
【讨论】:
修复了它。这是日期格式问题。它应该是 to_date(logdate, 'dd-MON-yy') - to_date(t, 'dd-MON-yy')logdate
已经是 DATE
。在 DATE
值上使用 TO_DATE
是没有用的。以上是关于SQL计算无工作天数的主要内容,如果未能解决你的问题,请参考以下文章