【问题标题】:Get Delivery Date 10 Business Days from Date of Request SQL从请求 SQL 之日起 10 个工作日内获取交货日期
【发布时间】:2012-12-18 02:00:23
【问题描述】:

我创建了一个“假期”表,其中包含 3 列 [Date] 的日期格式,[BusinessDay] 是 Y/N,[NameofDay] varchar(50) 是假期或周末。周六、周日、圣诞节都被标记为下一个十年。

我现在需要做的是弄清楚如何通过不计算工作日来确定需要完成请求的日期。我已经阅读了阅读和阅读,但没有看到任何有用的东西。在我的脑海里总是很简单。

我要解决的问题:如果请求是“优先”,则截止日期 [DueDate] 是从请求日期 [TransactionDate] 算起的 10 个工作日,或者如果是“关键”,则为 5 个工作日。

【问题讨论】:

  • 您应该创建日历表而不是假日表。日历表每个日期会有一行,并带有节假日、周末等指示符。这样可以更轻松地回答您的问题。
  • 我已经看到了一个很好的答案,但我想我也会分享这些链接以及有关执行此重要任务的有用信息:capricornexcel.co.uk/calculating-working-days-in-sqlcapricornexcel.co.uk/…
  • @GordonLinoff,我似乎无法粘贴我的数据样本。对不起。不过,我确实相信我也有您所指的内容。

标签: sql sql-server-2008 tsql ssms reporting-services


【解决方案1】:

由于@Andomar 提出的问题的递归性质,我建议另一种答案(它也恰好更简单,但需要可用的窗口函数)。它的作用是加入日历中晚于TransactionDate 的有效工作日,然后为每个请求id 根据需要查找第5 行或第10 行:

;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY id ORDER BY validDeliveryDate ASC) AS rn
   FROM (
       SELECT requests.*, holiday.Date as validDeliveryDate
       FROM requests
       JOIN holiday
         ON requests.TransactionDate < holiday.Date
         AND DATEADD(day, 25, requests.TransactionDate) >= holiday.Date
         AND holiday.BusinessDay = 'Y' ) v
)
SELECT *
FROM cte
WHERE rn = CASE WHEN critical = 1 THEN 5 ELSE 10 END

无需迭代 - 工作 sqlfiddle here

【讨论】:

  • 但是增加的日子本身可以是假期。答案错误。
  • @DavidStratton:周末不是在假期表中标记为非工作日吗?
  • 我认为这行不通。推进日子需要递归。所以这最初计算了假期的数量,但这可能包括更多的“假期”。
  • @GordonLinoff:是的,这就是 Andomar 所说的——我正在努力解决这个问题
  • 我认为这需要窗口化才能工作。 @donviti,你运行的是什么版本的 SQL Server?
【解决方案2】:

计算工作日需要迭代。您添加天数,然后减去非工作日,然后再添加。

一种方法是使用用户定义的函数:

if exists (select * from sys.objects where name ='WorkingDaysFrom' and type = 'FN')
    drop function dbo.WorkingDaysFrom
go
create function dbo.WorkingDaysFrom(
        @date date
,       @days int)
returns date
as
begin
        declare @result date = @date
        declare @remaining int = @days
        while @remaining > 0
                begin
                set     @result = dateadd(day, @remaining, @result)
                select  @remaining = count(*) 
                from    dbo.Holiday 
                where   [Date] between dateadd(day, 1-@remaining, @result) and @result
                        and BusinessDay = 'N'
                end
        return @result
end
go

Live example at SQL Fiddle. 打印:

TransactionDate   Priority    DueDate
2013-01-01        Priority    2013-01-16
2013-01-01        Critical    2013-01-09
2013-01-03        Priority    2013-01-17
2013-01-03        Critical    2013-01-10
2013-01-06        Priority    2013-01-18
2013-01-06        Critical    2013-01-11

【讨论】:

  • 你和我的不同之处在于我的Holiday表只包含有节假日的日期,并没有存储所有的日期。
  • @Andomar,为什么你的第一个 DueDate 是 01-16?我的解决方案是 01-15,我的数学似乎是正确的......
  • @bluefoot:我的 UDF 只使用非工作日,但 OP 包括他表中的所有日期。
  • @PinnyM:AFAIK 功能中的 10 个工作日意味着您可以在第 11 天交付。 10 天是 2,3,4,7,8,9,10,11,14,15 所以你在 16 号交货。
  • @Andomar 如果我完全阅读这个问题会很有帮助。是时候删除我的答案了。 :)
【解决方案3】:

我认为以下工作:

select t.*
from (select t.*,
             row_number() over (partition by r.requestid, IsWorkDay order by seqdate) as WorkDayNum
      from (select r.requestid, r.TransactionDate,
                   (case when critical = 1 then 5 else 10 end) as DaysToRespond,
                   dateadd(day, days.seqnum - 1, r.TransactionDate) as seqdate,
                   (case when h.date is null then 1 else 0 end) as IsWorkDay
            from requests r cross join
                 (select top 20 ROW_NUMBER() over (order by (select NULL)) as seqnum
                  from information_schema.columns
                 ) days left outer join
                 holidays h
                 on dateadd(day, days.seqnum - 1, r.TransactionDate) = h.date
           ) t
     ) t
where WorkDayNum = DaysToRespond and IsWorkDay = 1

这是未经测试的,但这是这个想法。

好吧,我已经对此进行了测试,在这种情况下它似乎返回了正确的结果:

with holidays as (
        select CAST('2012-01-01' as date) as date union all
        select CAST('2012-01-05' as date) as date union all
        select CAST('2012-01-06' as date) as date union all
        select CAST('2012-01-12' as date) as date union all
        select CAST('2012-01-13' as date) as date union all
        select CAST('2012-01-19' as date) as date union all
        select CAST('2012-01-20' as date) as date
       ),
      requests as (
        select 1 as requestId, CAST('2012-01-02' as DATE) as TransactionDate, 1 as Critical
      )

select t.*
from (select t.*,
             row_number() over (partition by t.requestid, IsWorkDay order by seqdate) as WorkDayNum
      from (select r.requestid, r.TransactionDate,
                   (case when critical = 1 then 5 else 10 end) as DaysToRespond,
                   dateadd(day, days.seqnum - 1, r.TransactionDate) as seqdate,
                   (case when h.date is null then 1 else 0 end) as IsWorkDay
            from requests r cross join
                 (select top 20 ROW_NUMBER() over (order by (select NULL)) as seqnum
                  from INFORMATION_SCHEMA.columns
                 ) days left outer join
                 holidays h
                 on dateadd(day, days.seqnum - 1, r.TransactionDate) = h.date
           ) t
     ) t
where WorkDayNum = DaysToRespond+1 and IsWorkDay = 1

此查询创建交易日期后 20 天的序列(20 天够吗?)。然后它计算这些天的日期并将日期与假期表进行比较。

计算天数是使用row_number() 按请求和工作日与非工作日进行分区。要选择的行是工作日和交易日期之后的天数。

【讨论】:

  • 编译错误。当我将r.requestid 替换为t.requestidholiday.date 替换为h.date 时,它返回一个空行集。
  • @Andomar 。 . .嗯,我已经测试过了。 count(*) 语法依赖于 SQL Server 2012,因此我将其更改为 row_number()。而且,它确实有效。我将days 子查询中的引用更改为information_schema.columns 而不是holidays。如果holidays 表的行数太少,那么它将不起作用。
  • SQL Fiddle 返回一个空行集?不知道我错过了什么。它设置为 SQL 2012。
  • @Andomar 。 . .这是因为 SQL Fiddle 上的 Information_Schema.Columns 为空。这仅用于创建数字列表。 . .您可以将其替换为(select 1 as seqnum union all select 2 union all select 3 . . . )。大多数数据库至少有几十列,因此这是更繁琐代码的捷径。
  • Updated version 只返回 1 行,而且日期还差得很远!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多