0

I have a tricky issue I am struggling with on a mental level.

In our db we have a table showing the UK Holidays for the next few years, and a stored function returns a recordset to my front end.

I have a flag in my recordset called 'deletable' which allows the frontend to decide if a context menu can be shown in the data grid, thus allowing that record to be deleted.

Currently the test (in my stored proc) just checks if the date column has a date from three days ago or more.

case when DATEDIFF(d,a.[date],GETDATE()) > 3 then 1 else 0 end as [deletable]

how can I modify that to find the previous working date by checking weekends and the Holidays table 'Holiday' column (which is a Datetime) and see if the [date] column in my recordset row is 3 working days before, taking into account Holidays from the Holidays table and weekends?

so if the [date] column is 23th May, and todays's date is 28th May, then that column returns 0, as the 27th was a bank holiday, whereas the next day it would return 1 because there would be more than 3 working days difference.

Is there an elegent way to do that?

thanks Philip

Our Man in Bananas
  • 5,809
  • 21
  • 91
  • 148

3 Answers3

2

Okay I'm totally refactoring this.

declare
    @DeletablePeriodStart datetime,
    @BusinessDays int

set @DeletablePeriodStart = dateadd(d,0,datediff(d,0,getdate()))
set @BusinessDays = 0

while @BusinessDays < 3
begin
    set @DeletablePeriodStart = dateadd(d,-1,@DeletablePeriodStart)
    if datepart(dw,@DeletablePeriodStart) not in (1,7) and
        not exists (select * from HolidayTable where Holiday = @DeletablePeriodStart)
    begin
        set @BusinessDays = @BusinessDays + 1
    end
end

This time it doesn't make any assumptions. It runs a quick loop checking whether each day is a valid business day and doesn't stop till it counts three of them. Then later just check whether a.[date] >= @DeletablePeriodStart

JC Ford
  • 6,946
  • 3
  • 25
  • 34
  • OP wants to take into account weekends too. – Hart CO Jun 07 '13 at 13:55
  • Easy enough to expand this approach to include weekends. I'll update. – JC Ford Jun 07 '13 at 13:57
  • @JC. thanks, how would I modify that to take into account weekends? Basically, if the date in the table row is more than 3 working days ago (taking into account holidays from the Holidays table and weekends) then this column in my dataset should be 1, else 0 as [deleteble] – Our Man in Bananas Jun 07 '13 at 14:02
  • I added some code that basically increments `@MaxAge` if the current date is Monday or Tuesday. You can adjust as necessary for your locale. Just check the `@@DateFirst` variable. If `@@DateFirst` is 1 (Monday) for your locale then `datepart(dw,GetDate())` will return 1 for a Monday. – JC Ford Jun 07 '13 at 14:06
  • @JC. it's for the UK, and on our server @@DateFirst **returns 7** – Our Man in Bananas Jun 07 '13 at 14:14
  • After further consideration there's still an issue with this. I'm going to refactor a bit and update. – JC Ford Jun 07 '13 at 14:17
  • Updated with a much more reliable approach. – JC Ford Jun 07 '13 at 14:29
  • @JC. thanks for your help - so in my column I just do `case when [date]>=@DeletablePeriodStart then 0 else 1` to show a record can be deleted as it's more than 3 working days old? – Our Man in Bananas Jun 07 '13 at 14:48
  • Yeah. There's no need to do complicated SQL acrobatics in your select. Really you just need to identify that start date and use it. (Though it looks like you have your 0 and 1 reversed) – JC Ford Jun 07 '13 at 14:49
1

You should substract the number of holidays between a.[date] and GETDATE() from the DATEDIFF. Try something like this:

case when DATEDIFF(d,a.[date],GETDATE())-(
    SELECT COUNT(*) FROM Holidays 
    WHERE HolidayDate BETWEEN a.[date] AND GETDATE()
)>3 then 1 else 0 end as [deletable]

Razvan

Razvan Socol
  • 5,426
  • 2
  • 20
  • 32
1

I am assuming that you don't have a Calendar table, although I'd highly recommend creating one, you can still achieve this without one:

The following will just get you a list of 2047 dates from yesterday going backwards (using the system table Master..spt_values):

WITH Dates AS
(   SELECT  Date = DATEADD(DAY, -number, CAST(GETDATE() AS DATE))
    FROM    Master..spt_values
    WHERE   type = 'P'
    AND     number > 0
)
SELECT  Dates.Date
FROM    Dates
ORDER BY Dates.Date DESC;

You then need to exclude weekends, and holidays from your table using this:

SET DATEFIRST 1;

WITH Dates AS
(   SELECT  Date = DATEADD(DAY, -number, CAST(GETDATE() AS DATE))
    FROM    Master..spt_values
    WHERE   type = 'P'
    AND     number > 0
)
SELECT  Dates.Date
FROM    Dates
WHERE   DATEPART(WEEKDAY, Dates.Date) <= 5
AND     NOT EXISTS
        (   SELECT  1
            FROM    HolidayTable h
            WHERE   Dates.Date = h.HolidayDate
        )
ORDER BY Dates.Date DESC;

N.B. You should explicitly set your DATEFIRST and not rely on server defaults

The above gives you a list of working days prior to today, you can then use the ROW_NUMBER() function, get the 3rd occurance in the list, giving a final query:

WITH Dates AS
(   SELECT  Date = DATEADD(DAY, -number, CAST(GETDATE() AS DATE))
    FROM    Master..spt_values
    WHERE   type = 'P'
    AND     number > 0
), WorkingDays AS
(   SELECT  Dates.Date, RN = ROW_NUMBER() OVER(ORDER BY Dates.Date DESC)
    FROM    Dates
    WHERE   DATEPART(WEEKDAY, Dates.Date) <= 5
    AND     NOT EXISTS
            (   SELECT  1
                FROM    HolidayTable h
                WHERE   Dates.Date = h.HolidayDate
            )
)
SELECT  WorkingDays.Date
FROM    WorkingDays
WHERE   RN = 3;

Or if you prefer this can be done with one query (exact same principle as above):

SELECT  d.Date
FROM    (   SELECT  Date = DATEADD(DAY, -number, CAST(GETDATE() AS DATE)), RN = ROW_NUMBER() OVER(ORDER BY number)
            FROM    Master..spt_values
            WHERE   type = 'P'
            AND     number > 0
            AND     DATEPART(WEEKDAY, DATEADD(DAY, -number, CAST(GETDATE() AS DATE))) <= 5
            AND     NOT EXISTS
                    (   SELECT  1
                        FROM    HolidayTable h
                        WHERE   DATEADD(DAY, -number, CAST(GETDATE() AS DATE)) = h.HolidayDate
                    )
        ) d
WHERE   rn = 3;
GarethD
  • 68,045
  • 10
  • 83
  • 123
  • 1
    Warning: `Master..spt_values` is an undocumented table, and could very well be removed from future versions of SQL Server. – TT. Jun 07 '13 at 14:46
  • 1
    @TT. It is "undocumented", and as such *could* be removed from future versions, however, as explained in [this answer](http://stackoverflow.com/a/4280038/1048425), it is used internally by SQL-Server, so they are not required to document it, but given it is used internally (e.g. `SP_LOCK`), I would not be overly worried about its removal. Writing future proof code has it's merits, but should not be the be all and end all of it. If it is a massive concern use `SELECT Number = ROW_NUMBER() OVER(ORDER BY (SELECT 1)) FROM sys.all_objects` to generate a sequential list on the fly. – GarethD Jun 07 '13 at 15:01
  • @GarethD +1 thanks, we have a Holidays table...and I tested your last code block by replacing `GETDATE()` with **'2013-05-28'** and it correctly returned '2013-05-22' which is nice... – Our Man in Bananas Jun 07 '13 at 15:12