Public holidays that fall on a sunday - sql

I dont know about other countries but in South Africa we have a public holiday on a Monday if the Sunday was a Public holiday. I need to write an Update statement that will return the date after x number of days and should the date be a monday after a public holiday it should add an additional day. My simple update statement looks like:
UPDATE tbl_ProjectTracker
set Predicted_Date = DATEADD(DAY, 20, Actual_Eng_Start_date)
I already have a table for referencing but I am not sure if I should determine the additional day on the table or on the Update statement.
Please assist

You should have a PublicHoliday table
Then you could Update predicted_date like that
DECLARE #PublicHoliday AS TABLE
(
HolidayDate date
)
DECLARE #NumberDaysPredict int = 20
UPDATE pt
set pt.Predicted_Date = DATEADD(DAY, #NumberDaysPredict + hl.NumberHolidaysOnSunday, pt.Actual_Eng_Start_date)
FROM tbl_ProjectTracker pt
CROSS APPLY
(
SELECT Count(*) AS NumberHolidaysOnSunday
FROM #PublicHoliday ph
WHERE ph.HolidayDate BETWEEN pt.Actual_Eng_Start_date AND DATEADD(DAY, #NumberDaysPredict, Actual_Eng_Start_date)
AND Datepart(dw,ph.HolidayDate) = 1 -- Sunday
) hl

A simple way would be a case construct for the number of days:
UPDATE tbl_ProjectTracker
set Predicted_Date = DATEADD(
DAY,
CASE WHEN Actual_Eng_Start_date IN
(select day from public_holidays where datepart(dw, day) = 1) THEN 21 ELSE 20 END,
Actual_Eng_Start_date)
Unfortunately SQL Server's date / time functions are weak to say the least. The result of DATEPART(dw, ...) depends on a setting (DATEFIRST), so the query is kind of unreliable. Always make sure the setting is correct when you run it. It has been requested to extend DATEPART such as to accept an optional parameter for DATEFIRST so you'd get a consistent query, but Microsoft has closed this as "unsolvable" (https://connect.microsoft.com/SQLServer/feedbackdetail/view/432303/datepart-dw-date-should-allow-optional-date-first-Parameter).

Related

SQL Server 2008 R2 - Creating a table of dates using a while statement. Looking for better options?

I'm trying to create a table of future dates. It will be easy to do with a while loop, but I know that isn't the most efficient way to do that in SQL. So I was hoping someone could share some ideas on how to do this in SQL set base manner.
Here is my code:
declare #count int, #dd date
set #count=0;
set #dd ='01/04/2013';
while (#count<24)
begin
select #dd=dateadd(week, 2,#dd);
set #count=#count+1;
select #dd
end
Thank you in advance...
Probably the best way to do this is with a fixed calendar table; go ahead and create a permanent table with all of the business rules you need for handling date logic.
As a workaround, you can do something like (assuming you have more than 24 columns in your database):
DECLARE #dd DATE
SET #dd = '01/04/2013';
SELECT TOP 24 DATEADD(week, 2*rn, #dd)
FROM (SELECT rn=(ROW_NUMBER() OVER (ORDER BY name)) -1
FROM sys.columns) c
We do something similar with our data warehouse. It's a two stage operation. One is to add new records with the primary key field only and the second is to update the other fields of the new records. The database engine is redbrick. redbrick syntax is similar to sql server syntax.
This is the insert query:
insert into period (date)
select
case when days_in_year((select max(date) from period))=365 -- current max year not leap year
and days_in_year(dateadd(day,1,(select max(date) from period)))=365 --new year not leap year
then dateadd(day,365,date)
else dateadd(day,366,date)end
from period
where date between
case when days_in_year(dateadd(day,1,(select max(date) from period)))=366 -- new year is leap year
or days_in_year((select max(date) from period))=366 -- current max year is leap year
then dateadd(day,-365, (select max(date) from period)) -- Dec 31 of year before current max year
else dateadd(day,-364, (select max(date) from period)) end --Jan 1 of current max year
and
case when days_in_year((select max(date) from period))=366 -- current max year is leap year
then dateadd(day,-1, (select max(date) from period))-- Dec 30 of current max year
else (select max(date) from period) end -- Dec 31 of current max year
and current_date > dateadd(month,-9,(select max(date) from period))
Note that days_in_year is a redbrick macro. In this case it's equivalent to a sql server user defined function. It's equivalent to this redbrick code
extract(dayofyear from date(concat(datename(year,%1),'-12-31')))
where %1 is the argument passed to the macro.

Sum of values per month divided by days of month

I have a Sales table:
id_item, quantity, date_time
What I need is to sum the items sold in a month and divide them by the days of the month from a selected period of months.
Example - The user selects the dates of Oct 1 to Dec 31. I need to show the items_sold/days_of_month:
Month Items sold Days of month Items/Day
Sep 25 30 0.83333
Oct 36 31 1.16
Dec 15 31 0.4838
I have to specify by Kind of item. the kind is obtained from another table called Items. I use dateformat dd/mm/yy.
select
month(date_time),
sum(quantity) / (select(datepart(dd,getdate())))
from
sales v
join items a on v.id_item=a.id_item
where
a.kind='Kind of Item'
and cast(Convert(varchar(10), date_time, 112) as datetime)
between '01/10/2012' and '31/12/2012'
group by
month(date_time)
My problem is selecting the days of the months, how can I select x number of months and divide the sum(quantity) of each month by the days of each?
I know this part of the code only selects the days of the current month:
(select(datepart(dd,getdate())))
Try this on for size:
DECLARE
#FromDate datetime,
#ToDate date; -- inclusive
SET #FromDate = DateAdd(month, DateDiff(month, 0, '20121118'), 0);
SET #ToDate = DateAdd(month, DateDiff(month, 0, '20121220') + 1, 0);
SELECT
Year = Year(S.date_time),
Month = Month(S.date_time),
QtyPerDay =
Sum(s.quantity) * 1.0
/ DateDiff(day, M.MonthStart, DateAdd(month, 1, M.MonthStart))
FROM
dbo.Sales S
INNER JOIN dbo.Items I
ON S.id_item = I.id_item
CROSS APPLY (
SELECT MonthStart = DateAdd(month, DateDiff(month, 0, S.date_time), 0)
) M
WHERE
I.kind = 'Kind of Item'
AND S.date_time >= #FromDate
AND S.date_time < #ToDate
GROUP BY
Year(S.date_time),
Month(S.date_time),
M.MonthStart
It will select any full month that is partially enclosed by the FromDate and ToDate. The * 1.0 part is required if the quantity column is an integer, otherwise you will get an integer result instead of a decimal one.
Some stylistic notes:
Do NOT use string date conversion on a column to ensure you get whole days. This will completely prevent any index from being used, require more CPU, and furthermore is unclear (what does style 112 do again!?!?). To enclose full date periods, use what I showed in my query of DateCol >= StartDate and DateCol < OneMoreThanEndDate. Do a search for "sargable" to understand a very key concept here. A very safe and valuable general rule is to never put a column inside an expression if the condition can be rewritten to avoid it.
It is good that you're aliasing your tables, but you should use those aliases throughout the query for each column, as I did in my query. I recognize that the aliases V and A came from another language so they make sense there--just in general try to use aliases that match the table names.
Do include the schema name on your objects. Not doing so is not a huge no-no, but there are definite benefits and it is best practice.
When you ask a question it is helpful to explain all the logic so people don't have to guess or ask you--if you know (for example) that users can input mid-month dates but you need whole months then please indicate that in your question and state what needs to be done.
Giving the version of SQL server helps us zero in on the syntax required, as prior versions are less expressive. By telling us the version we can give you the best query possible.
Note: there is nothing wrong with putting the date calculation math in the query itself (instead of using SET to do it). But I figured you would be encoding this in a stored procedure and if so, using SET is just fine.

Getting the last Monday of the current month using T-sql

I know this is quite a generic question but does anyone know a good way of checking if the date is the last monday of the month using T-SQL. I need to use it in a stored procedure to determine if the stored procedure returns data or does nothing.
Cheers!
The following select will return 1 if the current date is the last monday of the month, and 0 if not.
select
case
when datepart(dw, GETDATE()) = 2 and DATEPART(month, DATEADD(day, 7, GETDATE())) <> DATEPART(month, GETDATE())
then 1
else 0
end
datepart(dw, GETDATE()) returns the day of the week. Monday is 2. The second part adds 7 days to the current date and checks that within 7 days the month has changed (if it does not, it is not the last monday).
Change the GETDATE()'s to any date you want to check.
EDIT:
You can make it into a generic function and use it with any date you like:
CREATE FUNCTION
IsLastMondayOfMonth(#dateToCheck datetime)
RETURNS bit
AS
BEGIN
DECLARE
#result bit
SELECT #result =
CASE
WHEN datepart(dw, #dateToCheck) = 2 AND DATEPART(month, DATEADD(day, 7, #dateToCheck)) <> DATEPART(month, #dateToCheck)
THEN 1
ELSE 0
END
RETURN #result
END
Maybe something like this:
DECLARE #YourDate DATETIME='2012-02-25'
SELECT
CASE
WHEN #YourDate = DATEADD(wk, DATEDIFF(wk,0,DATEADD(month,DATEDIFF(MONTH,0,#YourDate),30)),0)
THEN 1
ELSE 0
END
Although you have already accepted another answer, an even simpler solution (in my opinion) is to use a calendar table that has a column called IsLastMondayOfMonth or something similar. Calendar tables tend to be much easier to maintain than functions because not only is the code much cleaner but also when an exception comes along ("because of an unusual public holiday this year we need to do our month-end processing one day later, I'm sure you can fix the system to do that?") you can just update a table to handle it instead of adding potentially awkward logic to your code.
This will select the date when the date is the last monday of the month regardless of firstdate. Making a function that rely on the database settings, is really not good practice.
select d
from (select '2012-02-20' d union all select '2012-02-27' union all select '2012-02-28') a
where datepart(day, dateadd(day, 7 ,d)) < 8 and datediff(day, 0, d) %7 = 0

SQL Recur every x weekday every x weeks

I'm trying to write a sql query which depending on what the user selects will recur every x day every x weeks.
So the user will select that they want the job to recur on tuesdays every 2 weeks
the values that are supplied are
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
I know how to set it for the amount of weeks:
SET #NextOccurance = (Convert(char(12),#StartDate + (#RecurrenceValue1),106))
But I'm not sure how to make it roll on to the day of the week so if the #startDate is today and it should recur every 2 weeks on a tuesday it will see that 2 weeks today is wednesday so will loop until it know that the day is tuesday and that will be the #NextRecurrance date.
Thanks in advance
An easy way to add a number of weeks to a date is to use (MSDN DATEADD)
DATEADD(wk, #StartDate, #recurrenceValue1)
To find out which day of the week the date you are looking at belongs, you can use (MSDN DATEPART)
DATEPART(dw, #StartDate)
This function uses DATEFIRST to determine which day of the week is the first one (http://msdn.microsoft.com/en-us/library/ms181598.aspx)
SET DATEFIRST 1 --Where 1 = Monday and 7 = Sunday
So for your problem (DATEFIRST being set to 1 = Monday)..
SET DATEFIRST 1
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
SET #StartDate = '2011-12-16' -- This is a Friday
SET #recurrenceValue1 = 2 -- In 2 weeks
SET #recurrenceValue2 = 2 -- On Tuesday
SET #NextOcurrance = DATEADD(wk, #recurrenceValue1, #StartDate) -- Add our 2 weeks
/* Check if our incrementation falls on the correct day - Adjust if needed */
IF (DATEPART(dw, #NextOcurrance) != #recurrenceValue2) BEGIN
DECLARE #weekDay int = DATEPART(dw, #NextOcurrance)
SET #NextOcurrance = DATEADD(dd, ((7 - #weekDay) + #recurrenceValue2), #NextOcurrance) -- Add to #NextOcurrance the number of days missing to be on the requested day of week
END
The logic for the number of days to add is as follow:
We have 7 days in a week, how many days does it take to reach the end of this week. Add this number of days to the #recurrenceValue2 (day of week we are looking for).
PS: I can't post more than 2 HyperLinks because of my reputation. This is why the DATEFIRST URL is in plain text.
Here's some code to allow certain specific date to be handled differently. Though this code is good only for unique dates. If ones needs to skip an entire week for instance, using this code will require to add values for each day of this week to skip. For ranges other than unique days, this code should be modified to handle date ranges or specific weeks and/or day of week.
CREATE TABLE OccurenceExclusions (
ExclusionDate DATE not null,
NumberOfDaysToAdd int not null
PRIMARY KEY (ExclusionDate)
)
INSERT OccurenceExclusions VALUES ('2012-01-01', 7)
SET #NextOcurrance = DATEADD(dd, COALESCE((SELECT NumberOfDaysToAdd
FROM OccurrenceExclusions
WHERE ExclusionDate = #NextOcurrance), 0), #NextOcurrance)
Probably the best solution would be to use a calendar table. http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
You could then query it like so:
SELECT TOP 1 #NextOccurance = Date
FROM CalendarTable
WHERE Date >= DATEADD(week, #recurranceValue1, #StartDate)
AND DateName = #recurranceValue2
Using the principles the #DanielM set out i changed it to fit my query see below:
BEGIN
SET DATEFIRST 1 --Where 1 = Monday and 7 = Sunday
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
-- sets #nextoccurence to next date after x amount of weeks
SET #NextOccurance = DATEADD(wk, #recurrenceValue1, #StartDate) -- Add on weeks /* Check if our incrementation falls on the correct day - Adjust if needed */
IF (DATEPART(dw, #NextOccurance) != #recurrenceValue2)
BEGIN
DECLARE #Weekday int = DATEPART(dw, #NextOccurance)
SET #NextOccurance = DATEADD(dd, (#RecurrenceValue2 - #Weekday), #NextOccurance) -- Add to #NextOcurrance the number of days missing to be on the requested day of week
END
END

Sql query needs to include bank holidays

I have the follwoing query:
set language 'english'
DECLARE #MyDate DATETIME
SET #MyDate = dateadd(dd,-1,dateadd(mm,datediff(mm,0,getdate()),0))
SELECT ReportEndDate = DATEADD(dd, CASE
WHEN DATENAME(weekday,#MyDate) = 'Saturday' THEN 5
WHEN DATENAME(weekday,#MyDate) IN ('Monday','Sunday') THEN 4
ELSE 6 END, #MyDate)
Which as you can see retuns the month end day + 4 working days.
I need to expand this to include Christmas and New Year. So that the above query takes those bank holidays into consideration when working out the 4th working day of the new month.
Any pointers would be much apreciated.
Build a calendar table.
http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html