donviti
donviti

Reputation: 294

Get Delivery Date 10 Business Days from Date of Request SQL

I've created a "holiday" table that has 3 columns [Date] in date format, [BusinessDay] that's a Y/N and [NameofDay] varchar(50) for the Holiday or Weekend. Saturday, Sunday, Christmas are all marked for the next decade.

What I now need to do is figure out how to determine the date a request needs to be completed by not counting the business days. I've read and read and read but don't see anything that has been useful. It's always simple in my head.

What I'm trying to solve: what will the Due Date [DueDate] be 10 business Days from the date of the request [TransactionDate] if the request is "priority", or 5 Business Days if it's "Critical".

Upvotes: 1

Views: 1755

Answers (3)

PinnyM
PinnyM

Reputation: 35531

Due to the recursive nature of the problem that @Andomar has brought up, I am suggesting an alternative answer (which happens to be much simpler as well, but requires windowing functions to be available). What this does is join valid business days from the calendar that are later than TransactionDate, and then for each request id find the 5th or 10th row as required:

;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

No iteration required - working sqlfiddle here

Upvotes: 4

Gordon Linoff
Gordon Linoff

Reputation: 1270443

I think the following works:

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

This is untested, but here is the idea.

Well, I have tested this, and it appears to return the right result in this case:

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

This query creates a sequence of 20 days following the transaction date (Is 20 enough?). It then calculates the date for these days and compares the date to the holiday table.

To count the number of days is uses row_number() partitioning by the request and workdays versus nonworkdays. The row to choose is the one that is a workday and the number of days after the transaction date.

Upvotes: 0

Andomar
Andomar

Reputation: 238186

Calculating business days requires iteration. You add a number of days, then subtract the non-business days, then add again.

One way to do that is a user-defined function:

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. This prints:

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

Upvotes: 1

Related Questions