【问题标题】:difference between two dates without weekends and holidays Sql query ORACLE没有周末和节假日的两个日期的区别sql查询ORACLE
【发布时间】:2015-10-02 11:15:59
【问题描述】:

我有 2 个表格:第一个包含采购订单的开始日期和结束日期, 第二张表包含年假

-采购订单

-假期

我正在尝试计算不包括周末和节假日的两个日期之间的工作日数。

输出应该是这样的:

Start Date | End Date | Business Days

你能帮帮我吗

【问题讨论】:

  • 请编辑您的问题并提供示例数据和所需的结果。
  • 听起来像是日历表的工作。

标签: sql oracle


【解决方案1】:

由于您已经有一个假期表,您可以计算开始日期和结束日期之间的假期,然后从结束日期和开始日期之间的天数差中减去。对于周末,您需要一个包含类似于您的假期表的周末天数的表,或者您可以按如下方式生成它们。

with sample_data(id, start_date, end_date) as (
  select 1, date '2015-03-06', date '2015-03-7' from dual union all
  select 2, date '2015-03-07', date '2015-03-8' from dual union all
  select 3, date '2015-03-08', date '2015-03-9' from dual union all
  select 4, date '2015-02-07', date '2015-06-26' from dual union all
  select 5, date '2015-04-17', date '2015-08-16' from dual
)
, holidays(holiday) as (
  select date '2015-01-01' from dual union all -- New Years
  select date '2015-01-19' from dual union all -- MLK Day
  select date '2015-02-16' from dual union all -- Presidents Day
  select date '2015-05-25' from dual union all -- Memorial Day
  select date '2015-04-03' from dual union all -- Independence Day (Observed)
  select date '2015-09-07' from dual union all -- Labor Day
  select date '2015-11-11' from dual union all -- Veterans Day
  select date '2015-11-26' from dual union all -- Thanks Giving
  select date '2015-11-27' from dual union all -- Black Friday
  select date '2015-12-25' from dual           -- Christmas
)
-- If your calendar table doesn't already hold weekends you can generate
-- the weekends with these next two subfactored queries (common table Expressions)
, firstweekend(weekend, end_date) as (
  select next_day(min(start_date),'saturday'), max(end_date) from sample_data
  union all
  select next_day(min(start_date),'sunday'), max(end_date) from sample_data
)
, weekends(weekend, last_end_date) as (
  select weekend, end_date from firstweekend
  union all
  select weekend + 7, last_end_date from weekends where weekend+7 <= last_end_date
)
-- if not already in the same table combine distinct weekend an holiday days
-- to prevent double counting (in case a holiday is also a weekend).
, days_off(day_off) as (
  select weekend from weekends
  union
  select holiday from holidays
)
select id
     , start_date
     , end_date
     , end_date - start_date + 1
     - (select count(*) from days_off where day_off between start_date and end_date) business_days
  from sample_data;

        ID START_DATE  END_DATE    BUSINESS_DAYS
---------- ----------- ----------- -------------
         1 06-MAR-2015 07-MAR-2015             1
         2 07-MAR-2015 08-MAR-2015             0
         3 08-MAR-2015 09-MAR-2015             1
         4 07-FEB-2015 26-JUN-2015            98
         5 17-APR-2015 16-AUG-2015            85

【讨论】:

    【解决方案2】:

    此查询应为purchase 表中的每个范围生成准确的工作日数:

    with days as (
      select rn, sd + level - 1 dt, sd, ed 
        from (select row_number() over (order by start_date) rn, 
                     start_date sd, end_date ed from purchase_order)
        connect by prior rn = rn and sd + level - 1 <= ed
               and prior dbms_random.value is not null)
    select sd start_date, ed end_date, count(1) business_days
      from days d left join holidays h on holiday_date = d.dt
      where dt - trunc(dt, 'iw') not in (5, 6) and h.holiday_date is null
      group by rn, sd, ed
    

    SQLFiddle demo

    对于purchase_orders 中的每一行,查询从这个范围内生成日期(这是由子查询dates 完成的)。 主查询检查这是周末还是节假日,并计算其余日期。

    如果purchase_orders 中有大量数据,用于生成日期的分层查询可能会导致速度变慢 或经期较长。在这种情况下,首选方法是创建日历表,正如 cmets 中已建议的那样。

    【讨论】:

      【解决方案3】:

      编辑:我忽略了 Oracle 标记的存在,并开始为 SQL Server 编写脚本。但是这个概念并没有改变。

      为了超级准确,我会创建一个如下格式的表格。

      Year int, month int, DaysInMonth int, firstOccuranceOfSunday int

      创建一个过程以从该表上的特定年份和月份中提取周末。

      CREATE FUNCTION [dbo].[GetWeekendsForMonthYear]
      (
          @year int,
          @month int
      )
      RETURNS @weekends TABLE
      (
      [Weekend] date
      )
      AS
      BEGIN
      
      declare @firstsunday int = 0
      Declare @DaysInMonth int = 0
      Select @DaysInMonth = DaysInMonth, @firstsunday = FirstSunday from Months
      Where [Year] = @year and [month] = @month
      Declare @FirstSaterday int = @firstsunday - 1
      
      declare @CurrentDay int = 0
      Declare @CurrentDayIsSunday bit = 0
      
      if @FirstSaterday !< 1
       Begin
          insert into @Weekends values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @Firstsaterday -1, 0))))
      
          insert into @Weekends values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @FirstSunday -1, 0))))
          set @CurrentDayIsSunday = 1
          set @CurrentDay = @firstsunday
       END
      else
          begin
           insert into @Weekends values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @FirstSunday -1, 0))))
           set @FirstSaterday = @firstsunday + 6
           insert into @Weekends values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @Firstsaterday -1, 0))))
           set @CurrentDayIsSunday = 0
          set @CurrentDay = @FirstSaterday
          end 
      
       declare @done bit = 0
      
       while @done = 0
       Begin
          if @CurrentDay <= @DaysInMonth
              Begin
                  If @CurrentDayIsSunday = 1
                  begin
                      set @CurrentDay = @CurrentDay + 6
                      set @CurrentDayIsSunday = 0
                      if @CurrentDay <= @DaysInMonth
                      begin
                          insert into @Weekends Values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @CurrentDay -1, 0))))
                      end
                  end
                  else
                      begin
                          set @CurrentDay = @CurrentDay + 1
                          set @CurrentDayIsSunday = 1
                          if @CurrentDay <= @DaysInMonth
                          begin
                              insert into @Weekends Values(DATEADD(year, @year -1900, DATEADD(month, @month -1, DATEADD(day, @CurrentDay -1, 0))))
                          end
                      end
                  end
          ELSE
          begin
              Set @done = 1
          end
       end
          RETURN
      END
      

      当调用并提供年份和月份时,这将返回代表周末的日期列表。

      现在,使用该函数,创建一个过程,为特定日期范围内的每个适用行调用一次该函数,并在临时表中返回值。

      请注意,我现在发布此内容,以便您查看发生了什么,但我将继续处理代码。当它们出现时,我会发布更新。

      更多内容:获取特定日期范围的周末列表(格式化),从该列表中删除可以在您的假期表中找到的所有日期。

      很遗憾,我明天还要上班,我要睡觉了。

      【讨论】:

      • 这不是 Oracle 语法。
      • hm.. 哎呀,我应该继续吗?也许有人可以基于此生成 Oracle 等价物。我非常有信心,这与即将到来的程序相结合,将在 SQL Server 中提供所需的结果。
      【解决方案4】:

      您可以使用如下查询删除非周末假期:

      select (t.end_date - t.start_date) - count(c.date)
      from table1 t left join
           calendar c
           on c.date between t1.start_date and t1.end_date and
              to_char(c.date, 'D') not in ('1', '7')
      group by t.end_date, t.start_date;
      

      删除周末的日子会更加复杂。整周有两个周末,所以这很容易。所以一个很好的近似值是:

      select (t.end_date - t.start_date) - (count(c.date) +
             2 * floor((t.end_date - t.start_date) / 7))
      from table1 t left join
           calendar c
           on c.date between t1.start_date and t1.end_date and
              to_char(c.date, 'D') not in ('1', '7')
      group by t.end_date, t.start_date;
      

      这没有得到星期几,这实际上是如果结束日期在开始日期之前,那么它在下一周。然而,这个逻辑在 Oracle 处理星期几的方式中变得相当复杂,所以也许上面的近似值就足够了。

      【讨论】:

      • 第二个查询只放节假日不放周末,应该补充什么?
      • @hejer 。 . .第二个应该删除两个日期之间整周的周末。
      • 我认为它正在起飞周日我希望我可以起飞周六和周日:)
      • @hejer 。 . . 2 * 应该在周日起飞。这种方法是一种近似。它可能在很长一段时间(几个月)内运行良好。如果您知道开始日期和结束日期都是工作日,则可以轻松修改。但是,在没有辅助表的情况下处理一周中的每一天开始变得复杂。有一个真正的日历表的建议是一个很好的建议。
      • 注意,to_char(c.date, 'D') 的结果取决于本地 NLS_TERRITORY 设置,可能会有所不同。更好地使用to_char(c.date, 'Dy', 'NLS_DATE_LANGUAGE = american' ) not in ('Sat', 'Sun')
      猜你喜欢
      • 2019-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-21
      • 1970-01-01
      • 1970-01-01
      • 2021-10-21
      • 1970-01-01
      相关资源
      最近更新 更多