Question

I have a table of dates call [BadDates], it has just one column where every record is a date to exclude. I have a UDF as follows:

CREATE FUNCTION [dbo].[udf_GetDateInBusinessDays]
(
  @StartDate datetime,  --Start Date
  @NumberDays int           --Good days ahead
)
RETURNS datetime
AS
BEGIN
-- Declare the return variable here
DECLARE @ReturnDate datetime
SET @ReturnDate = @StartDate
DECLARE @Counter int
SET @Counter = 0
WHILE   @Counter < @NumberDays
BEGIN
    SET @ReturnDate = DateAdd(d,1,@ReturnDate)
    IF ((SELECT COUNT(ID)
        FROM dbo.[BadDates]
        WHERE StartDate = @ReturnDate) = 0)
    BEGIN
        SET @Counter = @Counter + 1
    END
END
RETURN @ReturnDate
END

This UDF works great, but it is slow in processing. The stored procedure that uses this runs the UDF in every record. Is there other ways to provide this same functionality in a faster method.

Any help is greatly appreciated!

Was it helpful?

Solution

I haven't tested this but in theory it should work. I add up the number of days. Then I check if there were any baddates in that range. If there were I add the number of bad days and check if there were anymore baddates in the range I just added. Repeat until no bad dates.

CREATE FUNCTION [dbo].[udf_GetDateInBusinessDays]
(
  @StartDate datetime,  --Start Date
  @NumberDays int           --Good days ahead
)
RETURNS datetime
AS
BEGIN
-- Declare the return variable here
DECLARE @ReturnDate datetime
SET @ReturnDate = dateadd(d, @NumberDays, @StartDate);


DECLARE @d int;
SET @d = (select count(1) from baddates where startdate >= @StartDate and startdate <= @ReturnDate);

declare @t datetime;

WHILE   @d > 0
BEGIN
    set @t = @ReturnDate;
    set @ReturnDate = dateadd(d, @d, @ReturnDate);
    SET @d = (select count(1) from baddates where startdate > @t and startdate <= @ReturnDate);
END

RETURN @ReturnDate
END

OTHER TIPS

I'm assuming that what you are trying to do is calculate the date that is x working days past a given date.e.g. what date is 10 working days from today. I am also assuming that your baddates table contains non-working days e.g. Weekends and bank holidays.

I have encountered similar requirements in the past and usually ended up with days table that contains all possible dates along with a flag that indicates whether a particular date is a working day or not.

I then use that table to calculate what date is x working days from the provided date by selecting the record that is x days after the starting date.

So something like this

 CREATE TABLE all_days (  
  dated DATETIME,  
  day_state CHAR(1)  
  )

Where day_state is value of
D - Working Day
W - Weekend
B - Bank Holiday

The SQL to find the date after x working days then becomes

SELECT MAX(dated)
FROM (
  SELECT TOP(@number_days) dated
  FROM all_days
  WHERE day_state = 'D'
  AND dated >= @start_date
  ORDER by dated ASC
)

This code is untested but should give you the general idea. You may not want to differentiate between weekends and public holidays in which case you could rename the day_state to working_day and make it a BIT field.

You should create a composite unique index on dated and day_state.

You may want to put an index on BadDates.StartDate, but there may be other, better solutions.

Ok why are you counting when you can use the EXISTS keyword? If its because you can have multiple dates of the same type in Badates this seems wrong. COUNT will probably looking through the whole table to count the instances of startdate when all you need is 1 to exclude.

Have you had a look at the query plan to see what is happening?

Looks like you are using this UDF to calculate the difference between two dates. If i am interpreting this correctly then I would recommend that you use the built-in datediff function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top