sql - 在oracle SQL中计算没有日历表的工作日(包括日期之间的假期)

标签 sql oracle

好吧,我已经阅读了大量关于在 sql 中模拟 excel 的 networkdays 函数的可能性的文章,并得出结论,到目前为止最简单的解决方案是拥有一个日历表来标记工作天或非工作日。然而,由于我无法控制的情况,我们无法享受如此奢侈的享受,而且在不久的将来我们也不太可能享受到这种奢侈。

目前,我已经设法将 SQL 中毫无疑问是一个可怕的低效查询拼凑在一起,但它确实有效 - 问题是,它一次只能对单个客户端记录有效。

SELECT O_ASSESSMENTS.ASM_ID,
       O_ASSESSMENTS.ASM_START_DATE,
       O_ASSESSMENTS.ASM_END_DATE,
      sum(CASE 
              When TO_CHAR(O_ASSESSMENTS.ASM_START_DATE + rownum -1,'Day')
                   = 'Sunday   ' THEN 0
              When TO_CHAR(O_ASSESSMENTS.ASM_START_DATE + rownum -1,'Day')
                   = 'Saturday ' THEN 0
              WHEN O_ASSESSMENTS.ASM_START_DATE + rownum - 1
                   IN ('03-01-2000','21-04-2000','24-04-2000','01-05-2000','29-05-2000','28-08-2000','25-12-2000','26-12-2000','01-01-2001','13-04-2001','16-04-2001','07-05-2001','28-05-2001','27-08-2001','25-12-2001','26-12-2001','01-01-2002','29-03-2002','01-04-2002','06-04-2002','03-06-2002','04-06-2002','26-08-2002','25-12-2002','26-12-2002','01-01-2003','18-04-2003','21-04-2003','05-05-2003','26-05-2003','25-08-2003','25-12-2003','26-12-2003','01-01-2004','09-04-2004','12-04-2004','03-05-2004','31-05-2004','30-08-2004','25-12-2004','26-12-2004','27-12-2004','28-12-2004','01-01-2005','03-01-2005','25-03-2005','28-03-2005','02-05-2005','30-05-2005','29-08-2005','27-12-2005','28-12-2005','02-01-2006','14-04-2006','17-04-2006','01-05-2006','29-05-2006','28-08-2006','25-12-2006','26-12-2006','02-01-2007','06-04-2007','09-04-2007','07-05-2007','28-05-2007','27-08-2007','25-12-2007','26-12-2007','01-01-2008','21-03-2008','24-03-2008','05-05-2008','26-05-2008','25-08-2008','25-12-2008','26-12-2008','01-01-2009','10-04-2009','13-04-2009','04-05-2009','25-05-2009','31-08-2009','25-12-2009','28-12-2009','01-01-2010','02-04-2010','05-04-2010','03-05-2010','31-05-2010','30-08-2010','24-12-2010','27-12-2010','28-12-2010','31-12-2010','03-01-2011','22-04-2011','25-04-2011','29-04-2011','02-05-2011','30-05-2011','29-08-2011','26-12-2011','27-12-2011') 
                   THEN 0 
              ELSE 1 
          END)-1 AS Week_Day
From O_ASSESSMENTS,
     ALL_OBJECTS
WHERE O_ASSESSMENTS.ASM_QSA_ID  IN ('TYPE1')
  AND O_ASSESSMENTS.ASM_END_DATE >= '01/01/2012'
  AND O_ASSESSMENTS.ASM_ID = 'A00000'
  AND ROWNUM <= O_ASSESSMENTS.ASM_END_DATE-O_ASSESSMENTS.ASM_START_DATE+1
GROUP BY
      O_ASSESSMENTS.ASM_ID,
      O_ASSESSMENTS.ASM_START_DATE,
      O_ASSESSMENTS.ASM_END_DATE

基本上,我想知道是否 a) 我应该停止在这方面浪费时间,或者 b) 是否有可能让它为多个客户工作?任何指示表示感谢!

编辑:进一步说明 - 我已经使用 Excel 计算出了时间表,但如果我们可以在报告中进行计算,那就更理想了,因为我们希望最终用户能够无需进一步运行就有问题的报告操纵。

编辑:

MarkBannister 的答案非常有效,尽管速度很慢(尽管我已经预料到它不是首选解决方案) - 现在的挑战在于我将其集成到现有报告中!

with
calendar_cte as (select
to_date('01-01-2000')+level-1 calendar_date,
case when to_char(to_date('01-01-2000')+level-1, 'day') in ('sunday   ','saturday ') then 0 when to_date('01-01-2000')+level-1 in ('03-01-2000','21-04-2000','24-04-2000','01-05-2000','29-05-2000','28-08-2000','25-12-2000','26-12-2000','01-01-2001','13-04-2001','16-04-2001','07-05-2001','28-05-2001','27-08-2001','25-12-2001','26-12-2001','01-01-2002','29-03-2002','01-04-2002','06-04-2002','03-06-2002','04-06-2002','26-08-2002','25-12-2002','26-12-2002','01-01-2003','18-04-2003','21-04-2003','05-05-2003','26-05-2003','25-08-2003','25-12-2003','26-12-2003','01-01-2004','09-04-2004','12-04-2004','03-05-2004','31-05-2004','30-08-2004','25-12-2004','26-12-2004','27-12-2004','28-12-2004','01-01-2005','03-01-2005','25-03-2005','28-03-2005','02-05-2005','30-05-2005','29-08-2005','27-12-2005','28-12-2005','02-01-2006','14-04-2006','17-04-2006','01-05-2006','29-05-2006','28-08-2006','25-12-2006','26-12-2006','02-01-2007','06-04-2007','09-04-2007','07-05-2007','28-05-2007','27-08-2007','25-12-2007','26-12-2007','01-01-2008','21-03-2008','24-03-2008','05-05-2008','26-05-2008','25-08-2008','25-12-2008','26-12-2008','01-01-2009','10-04-2009','13-04-2009','04-05-2009','25-05-2009','31-08-2009','25-12-2009','28-12-2009','01-01-2010','02-04-2010','05-04-2010','03-05-2010','31-05-2010','30-08-2010','24-12-2010','27-12-2010','28-12-2010','31-12-2010','03-01-2011','22-04-2011','25-04-2011','29-04-2011','02-05-2011','30-05-2011','29-08-2011','26-12-2011','27-12-2011','01-01-2012','02-01-2012') then 0 else 1 end working_day
from dual
connect by level <= 1825 + sysdate - to_date('01-01-2000') ) 
SELECT 
a.ASM_ID,
a.ASM_START_DATE,
a.ASM_END_DATE,
sum(c.working_day)-1 AS Week_Day 
From 
O_ASSESSMENTS a 
join calendar_cte c
on c.calendar_date between a.ASM_START_DATE and a.ASM_END_DATE 
WHERE a.ASM_QSA_ID  IN ('TYPE1')
and a.ASM_END_DATE >= '01/01/2012'
GROUP BY      
a.ASM_ID,
a.ASM_START_DATE,
a.ASM_END_DATE 

最佳答案

有几种方法可以做到这一点。也许最简单的方法是创建一个 CTE,根据 Oracle 的 connect by 语法生成虚拟日历表,然后将其连接到 Assesments 表,如下所示:

with calendar_cte as (
select to_date('01-01-2000')+level-1 calendar_date,
       case when to_char(to_date('01-01-2000')+level-1, 'Day') 
                in ('Sunday   ','Saturday ') then 0
            when to_date('01-01-2000')+level-1
                in ('03-01-2000','21-04-2000','24-04-2000','01-05-2000','29-05-2000','28-08-2000','25-12-2000','26-12-2000','01-01-2001','13-04-2001','16-04-2001','07-05-2001','28-05-2001','27-08-2001','25-12-2001','26-12-2001','01-01-2002','29-03-2002','01-04-2002','06-04-2002','03-06-2002','04-06-2002','26-08-2002','25-12-2002','26-12-2002','01-01-2003','18-04-2003','21-04-2003','05-05-2003','26-05-2003','25-08-2003','25-12-2003','26-12-2003','01-01-2004','09-04-2004','12-04-2004','03-05-2004','31-05-2004','30-08-2004','25-12-2004','26-12-2004','27-12-2004','28-12-2004','01-01-2005','03-01-2005','25-03-2005','28-03-2005','02-05-2005','30-05-2005','29-08-2005','27-12-2005','28-12-2005','02-01-2006','14-04-2006','17-04-2006','01-05-2006','29-05-2006','28-08-2006','25-12-2006','26-12-2006','02-01-2007','06-04-2007','09-04-2007','07-05-2007','28-05-2007','27-08-2007','25-12-2007','26-12-2007','01-01-2008','21-03-2008','24-03-2008','05-05-2008','26-05-2008','25-08-2008','25-12-2008','26-12-2008','01-01-2009','10-04-2009','13-04-2009','04-05-2009','25-05-2009','31-08-2009','25-12-2009','28-12-2009','01-01-2010','02-04-2010','05-04-2010','03-05-2010','31-05-2010','30-08-2010','24-12-2010','27-12-2010','28-12-2010','31-12-2010','03-01-2011','22-04-2011','25-04-2011','29-04-2011','02-05-2011','30-05-2011','29-08-2011','26-12-2011','27-12-2011') 
                then 0 
            else 1
       end working_day
from dual
connect by level <= 36525 + sysdate - to_date('01-01-2000') )
SELECT a.ASM_ID,
       a.ASM_START_DATE,
       a.ASM_END_DATE,
       sum(c.working_day) AS Week_Day
From O_ASSESSMENTS a
join calendar_cte c 
  on c.calendar_date between a.ASM_START_DATE and a.ASM_END_DATE
WHERE a.ASM_QSA_ID  IN ('TYPE1') and 
      a.ASM_END_DATE >= '01/01/2012' -- and a.ASM_ID = 'A00000'
GROUP BY
      a.ASM_ID,
      a.ASM_START_DATE,
      a.ASM_END_DATE

这将生成一个虚拟表,其中填充的日期从 2000 年 1 月 1 日到当前日期后 10 年,所有周末都标记为非工作日,并且在第二个 in 子句中指定的所有日期 (即截至 2011 年 12 月 27 日)也标记为非工作日。

此方法(或将假日日期硬编码到查询中的任何方法)的缺点是每次定义新的假日日期时,使用此方法的每个查询都必须添加这些日期。

关于sql - 在oracle SQL中计算没有日历表的工作日(包括日期之间的假期),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8892683/

相关文章:

php - 尝试从 SQL 返回 true 或 false

java - 将 jOOQ 与 Google Cloud SQL 结合使用

oracle - 在Oracle中查找存储过程的依赖元素

database - 审计表中是否需要 id 列?

sql - ORA-06502 :PL/SQL : Numeric or Value Error:Bulk Bind : truncated Bind

sql - 如何将数据库行转换为结构

sql - SQL Server中的更新语句中的错误

sql - 如何使用日期打破总和结果的平局?

SQL 'FROM' 不在预期位置

oracle - 将 keep dense_rank 从 Oracle 查询转换为 postgres