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计算无工作天数的主要内容,如果未能解决你的问题,请参考以下文章

SQL语句计算两个日期之间有多少个工作日的方法

(013)每日SQL学习:确定两个日期之间的工作日天数和计算一年周内各日期次数

SQL是计算两个日期相差多少天数的函数?

SQL:连续天数的计算方法

sql 计算SQL中的天数

SQL 计算连续天数