TSQL Determine every other Friday from a "seed" date - sql-server

Greetings StackOverflow Wizards.
SQL datetime calculations always give me trouble. I am trying to determine if an employee's hiredate fell between the last payday of that month and the first of the next month. (I.e. did they get a paycheck in their hire month.
Knowns:
I know our paydays are every other Friday.
I know 01/02/1970 was a Payday, and that date precedes the longest
active employee we have.
I know the hire date of each active employee (pulled from table).
I know (can calculate) the first of the month following the hire
date.
What I cannot seem to wrap my head around is how to use that seed date (01/02/1970) with datediff, dateadd, datepart, etc. to determine if there is a pay date between the hire date in question and the first of the following month.
In pseudo-code, here is what I'm trying to do:
declare #seedDate datetime = '01/02/1970' -- Friday - Payday seed date from which to calculate
declare #hireDate datetime = '09/26/2008' -- this date will actually be pulled from ServiceTotal table
declare #firstOfMonth datetime = DATEADD(MONTH, DATEDIFF(MONTH, 0, #hireDate) + 1, 0) -- the first of the month following #hireDate
declare #priorPayDate datetime -- calculate the friday payday immediately prior to #firstOfMonth
if #priorPayDate BETWEEN #hireDate AND #firstOfMonth
begin
-- do this
end
else
begin
-- do that
end
Using the hard-coded #hireDate above, and the #seedDate to determine every-other-Friday paydays, I know that there was a payday on 9/19/2008 and not another one until 10/03/2008, so the boolean above would be FALSE, and I will "do that" rather than "do this." How do I determine the value of #priorPayDate?

In all my databases where there is a lot going on with dates I create a table with colums for date,day, weekday,month,weeknr,dayof month, etc etc. I then use a procedural programming language or a bunch of handwritten sql to populate this table with every day for a large range of years say 1970 to 2200.
I pack this table 100% and index it heavily. You can then simply join any date to this table and do complex date stuff with simple where clause. So basically you pre calculate a helper table. maybe in you case you add a column to the date helper table with friday since seed column.
hope that makes sense.

Taking a DATEDIFF for days between your #seedDate and #firstOfMonth will give you a total number of days, which you can modulus by number of days between pay periods (14) to get number of days from the last pay period to the #firstOfMonth. You'll run into problems when the 1st is a payday (e.g. next month), which makes a CASE statement necessary:
DECLARE #priorPayDate DATETIME
SET #priorPayDate = CASE
WHEN DATEDIFF(dd, #seedDate, #firstOfMonth) % 14 = 0
THEN DATEADD(dd, -14, #firstOfMonth)
ELSE DATEADD(dd, -(DATEDIFF(dd, #seedDate, #firstOfMonth) % 14), #firstOfMonth)
END

Related

Return dates from last/this week depending on the current time and when "production" started

Sorry if the Title is confusing but it's hard to explain what I'm after in one phrase.
I'm currently producing a report based on the production for the week. I start off my CTE construction with the following to get the days Monday to Friday of the current week:
WITH
cte_Date AS
(
SELECT
CAST(DateTime AS date) AS Date
FROM
( VALUES
(GETDATE()
)
, (DATEADD(day,-1,GETDATE()))
, (DATEADD(day,-2,GETDATE()))
, (DATEADD(day,-3,GETDATE()))
, (DATEADD(day,-4,GETDATE()))
, (DATEADD(day,-5,GETDATE()))
, (DATEADD(day,-6,GETDATE())) ) AS LastSevenDays(DateTime)
WHERE
DATENAME(weekday, DateTime) = 'Monday'
UNION ALL
SELECT
DATEADD(day,1,Date)
FROM
cte_Date
WHERE
DATENAME(weekday,Date) <> 'Friday'
)
This is working fine. I have made the report available to users so they can run it anytime however sometimes nobody is available to run it last thing Friday. This means they don't get to see the full production for Friday and then the following week the CTE days change.
I'm trying to keep this a one-click affair so rather than introduce date parameters I proposed to the users that we adjust the query such that if they run the report before midday on "Monday" then it will show them last week's figures and they were happy with this (me and my big mouth). I put Monday in quotes because what we really mean of course is the first production day of the week.
My primary data table (which we'll call MyData) has a datetime field named DateTime (really!) that I can reference to determine the first day of production for the week.
One final caveat: Due to the layout of the report the users insisted that they always want to see the five days Monday to Friday, even if there is no production on a given day. (Consequently I do a LEFT JOIN from cte_Date to all other tables required.) So to be clear, right now as I'm typing this it's 11:45am local time on Tuesday and yesterday happened to be a public holiday here so running the report now should return Monday to Friday last week, but running it in 20 minutes time should return Monday to Friday this week.
Please help, my poor brain is getting twisted trying to figure it out.
There are a few different ways you can tackle this, but they all boil down to the same thing: you need a way of figuring out whether it's before or after 12pm on the first working day of the current week, then you need to get the Monday of the current "production week".
Let's just say, for simplicity's sake, you have some sort of table that contains public holidays (or non-production days). To find out whether it's the first day of the current production week, you basically just have to add the number of days in a row since the start of the week that have been public holidays.
Then you need to figure out whether it's before or after 12pm of that day.
If it's before you want last week's Monday-Friday. If it's after, you want this week's Monday-Friday.
Here's one way you might do this:
DECLARE #NonProductionDays TABLE (NPD DATE UNIQUE NOT NULL); -- Public holiday table.
INSERT #NonProductionDays (NPD) VALUES ('2017-09-25');
DECLARE #i INT = -- You don't need a variable for this, but just to keep things simple...
(
SELECT COUNT(*) -- Extract number of public holidays in a row this week before current date.
FROM #NonProductionDays AS N
WHERE DATEDIFF(WEEK, 0, N.NPD) = DATEDIFF(WEEK, 0, GETDATE())
AND N.NPD <= GETDATE()
AND (DATENAME(WEEKDAY, N.NPD) = 'Monday' OR EXISTS (SELECT 1 FROM #NonProductionDays AS N2 WHERE N2.NPD = DATEADD(DAY, -1, N.NPD)))
);
SELECT D = CAST(DATEADD(DAY, T.N, DATEADD(WEEK, DATEDIFF(HOUR, DATEADD(DAY, #i, '1900-01-01 12:00:00'), GETDATE()) / 24 / 7, '1900-01-01')) AS DATE)
FROM (VALUES (0), (1), (2), (3), (4)) AS T(N);
/*
Breaking this down:
X = DATEADD(DAY, #i, '1900-01-01 12:00:00')
-- Adds the number of NPD days this week to '1900-01-01 12:00:00'
-- So, for example, X would be '1900-01-02 12:00:00' this week
Y = DATEDIFF(HOUR, X, GETDATE()) / 24 / 7
-- The number of weeks between X and now, by taking the number of hours and dividing by 24 then by 7
-- The division is necessary to compare the hour.
-- So, for example, as of 11am on the September 26 2017, you'd get 6142.
-- As of 12pm on September 26 2017, you'd get 6143.
Z = DATEADD(WEEK, Y, '1900-01-01')
-- Just adds Y weeks to 1900-01-01, which was a Monday. This tells you the Monday of the current "production week".
-- So, for example, as of 11am on September 26 2017, you'd get '2017-09-18 00:00:00.000'.
-- As of 12pm on September 26 2017, you'd get '2017-09-25 00:00:00.000'.
Then we cast this as a date and add 0/1/2/3/4 days to it to get Monday, Tuesday, Wednesday, Thursday and Friday of the current "production week".
*/
I'm not sure I came up with the most efficient approach, but after a week of tossing it about in my brain this is what I came up with. I approached the problem from the opposite direction of that suggested by #ZLK.
My existing logic was already giving me the Monday of this week so in a subquery I looked for the first production record after Monday, stripped off the time with a DATEDIFF and made it midday with a DATEADD. I was then able to compare the current Date/Time with midday of the first production day to determine whether to reduce the date by one week.
I replaced this SELECT clause:
SELECT
CAST(DateTime AS date) AS Date
with this one:
SELECT -- Monday this week if it's after midday on the first production day otherwise Monday last week
DATEADD(week,IIF(GETDATE()>=DATEADD(hour,12,(
SELECT DATEDIFF(day,0,MIN(DateTime))
FROM MyData
WHERE CAST(MyData.DateTime AS date) >= CAST(LastSevenDays.DateTime AS date)
)),0,-1),CAST(LastSevenDays.DateTime AS date)) AS Date
To cater for the case where a new week has commenced but the operator runs the report before production starts I carefully arranged the boolean condition inside my IIF clause so that the empty result set from the subquery would mean the test returned FALSE and the operator would still see last week's figures.
(#ZLK, Thanks for your input - you did help my thinking a bit but I don't think your answer should be marked as correct. What I've come up with here is what I was originally requesting and didn't require the use of a static table.)

dynamically changing dates in a temp table

So i have a very specific task. I run some sql statements within which there is a temp table which contains date ranges specific to countries.
e.g.
INSERT INTO #dateRange(durationDesc, contryCode,startDate,endDate)
VALUES ('Weekly - TY','UK','20160919','20160925')
,('Weekly - LY','UK','20150921','20150927')
,('Weekly - LW','UK','20150912','20150918')
So, corresponding week previous year. The other range is month to date.
Whats the best way to do this? I'd prefer one where i only need to enter one date and the rest can be updated.
Any questions, feel free to ask.
You can always get current system datetime by GETDATE(), and then modify it accordingly by DATEADD()
For example:
SELECT DATEADD(YEAR, -1, GETDATE()) -- Result in 1 year prior the current system datetime.
SELECT DATEADD(MONTH, 13, GETDATE()) -- Result in 13 months after the current system datetime.
In your code snippet you seems to be converting the datetime into yyyymmdd string format (Although I highly doubt the necessity), which can be achieve by a CONVERT:
SELECT CONVERT(VARCHAR, DATEADD(YEAR, -1, GETDATE()), 112)
For example if today is 10/26/2016, then it should show result as 1 year prior to today in yyyymmdd format, 20151026

Date filter on column in sql

I have query that filtered by date, for now it take only the last 24h from the moment I execute it, for doing that I'm using the next code:
( DateDiff(HH, vw_public_task.complete_date, getdate()) < 25)
There is a way that my date filter will give query results for the last 24h but not depending on my current hour but according to "day 08:00am" -- "day+1 08:00am" at any time that I execute it?.
For example if I execute my query now I want to see date results from yesterday 08:00am till today 08:00am.
You can calculate yesterday 8am using the formula:
-- Yesterday at 8 am.
SELECT
DATEADD(HOUR, 8, CAST(CAST(DATEADD(DAY, -1, GETDATE()) AS DATE) AS DATETIME)) AS Yesterday8AM
;
GetDate returns the current date. The innermost date add subtracts one day. Casting this as a date removes the timestamp. Casting this back to a DateTime gives yesterday at midnight. Now we are dealing with a DateTime we can use date add, again, to add 8 hours.
If you are using SQL Server 2012, or above, consider the native function DATETIME2FROMPARTS instead.
Use Date() (How to part DATE and TIME from DATETIME in MySQL).
( DateDiff(HH, vw_public_task.complete_date, Date(getdate())+8 ) < 25)

SQL Server: calculate working time

How I can calculate the working time in SQL Server between two datetime variables, excluding the holidays?
Any ideas?
Holidays aren't universal - they depends very much on your location. Not even the fact which days of the week are "working" days is the same - it depends on your location.
Because of that, a general, universal answer will not be possible, and for that reason, there's also no system-provided function in T-SQL for doing this. How would SQL Server know what holidays you have in your corner of the world??.
You need to have a table of your holidays somewhere in your system and handle it yourself.
Some posts that might be of some help to you:
Calculate Number of Working Days in SQL Server: this just basically removes any Saturdays and Sundays - but doesn't include other holidays
How do I count the number of business days between two dates? : shows the same main approach, with the addition of a table that contains other holidays like Easter, 4th of July (US National Holiday) and so on
Like marc_s says, you currently need a custom solution. I really hope Microsoft adds some standard functionality: it's tough to get right, and holidays are pretty much standardized by location.
Here's an example:
declare #start_date datetime
declare #end_date datetime
set #start_date = '2010-12-20'
set #end_date = '2010-12-26'
-- A table with all non-working days. This just adds Christmass, but you
-- probably should add weekends as well.
declare #non_working_days table (dt datetime)
insert #non_working_days values ('2010-12-25'), ('2010-12-26')
-- Remove the time part
set #start_date = DATEADD(D, 0, DATEDIFF(D, 0, #start_date))
set #end_date = DATEADD(D, 0, DATEDIFF(D, 0, #end_date))
-- Find the number of non-working-days
declare #nwd_count int
select #nwd_count = count(*)
from #non_working_days
where dt >= #start_date and dt < #end_date
-- Print result
select datediff(DAY, #start_date, #end_date) - #nwd_count
This prints 5, because the 25th is not a working day.
Have a table which has a row for every date you're interested in, and, say, a "working hours" column, or just a "working day" indicator if you want to do it at day granularity. (I find this approach makes the final SQL simpler, plus enables all sorts of other useful queries, but then I'm into data warehousing, rather than operational databases, so you may find the "just list the holidays" approach better, depending...)
You will, of course, have to create that table yourself, working from some feed of holiday dates for the region you're interested in.
Typically you can project these forward at least a year, as most public holidays are agreed a long way in advance (though there are some that pop up at the "last minute" -- in the UK, for example, 29 April will be an extra public holiday in 2010, as there's a royal wedding taking place, and we got less than a year's notice of that.
Then you just
SELECT
SUM(working_hours)
FROM
all_dates
WHERE
the_date BETWEEN #start_date AND #end_date
If you want to do this internationally, it gets incredibly difficult to get your data; there's no sensible source that I know of for international holiday dates, and different regions in a "country" might have different dates -- e.g. you may know that someone's in the United Kingdom, but unless you know if they're in Scotland or not, you won't know if the first two days of the year are a public holiday, or just the first...

TSQL Function to calculate 30 WORKING days Date from a Specified Date (SQL Server 2005)

TSQL Function to calculate 30 WORKING days Date from a Specified Date (SQL Server 2005)?
Input parameters would be Date and Number of Working Days.
Output would be the Calculated Date.
This would exclude Saturday, Sunday, Holidays and Day Holiday was observered.
i.e. If the Holiday falls on a weekend but it is observed on the Friday or Monday after the holiday.
For the Holidays we have a table with holiday and day it is being observed date.
Have a look at this article by Celko - in general you need to "pre-calculate" a calendar table to take in account all possible vagaries like Easter, bank holidays etc.
There's one right in the SQL online help if you scroll down to UDF to return the number of business days, including a check to a bank holidays table
you can tweak this.
Instead of writing a tsql function, it might easier if you build a table that's similar to the Date Dimension (DimDate) table in data warehouse. DimDate would contain a column named isHoliday. You can also add other columns that might be useful. Then you write a script to populate DimDate
Then you can run a query off it.
I don't have a table of holidays handy, so I haven't tested this very much - but as nobody else has attempted an answer, here's how I'd start:
declare #tempDate datetime,
#StartDate datetime,
#WorkingDays int,
#NonWorkingDays int,
#TargetDate datetime
set #StartDate = '2010-10-26' --Change this to a paramter
set #WorkingDays = 9 --Change this to a parameter
set #NonWorkingDays = -1
/*Work out the answer ignoring holidays */
set #tempDate = dateadd(d,#WorkingDays,#StartDate)
while (dateadd(d,#WorkingDays + #NonWorkingDays, #StartDate) < #tempDate)
begin
/*Work out how many holidays are in the interval we've worked out*/
select #NonWorkingDays = count(HolidayDate)
from Holidays
where HolidayDate between #StartDate and #tempDate;
/*Extend the interval to include the holidays we've just found*/
set #tempDate = dateadd(d,#NonWorkingDays,#tempDate)
/*See if #NonWorkingDays has changed with the new #tempDate*/
select #NonWorkingDays = count(HolidayDate)
from Holidays
where HolidayDate between #StartDate and #tempDate;
end
set #TargetDate = dateadd(d,#WorkingDays + #NonWorkingDays, #StartDate)
print 'Target Date: ' + cast(#TargetDate as varchar(50))
Note this only works for Holidays at the moment - not weekends. You'd have to load all weekends into the holiday table (or join to a weekends table or use the DATENAME function) but the calculation should be the same.
Not sure how your Holiday table handles duplicate dates (eg. Boxing Day and St Stephen's Day both fall on 26th Dec) so you might need to take account of that.

Resources