Getting the last Monday of the current month using T-sql - 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

Related

SQL Changing Date by one workday and filter for it

I'm running the following query
SELECT a.DAT
FROM a
and getting a column of datetimes. I would like to change this column or alternatively add a column, that adds a workday to the corresponding datetime and I want to be able to filter for the date at the same time
My approach for changing the workday is like this CASE WHEN DATENAME(dw, date) = 'Friday' THEN DATEADD(dw, 3, date), but I don't know how to fit that in. Here I'm adding 3 days to Friday because it should skip to Monday the next workday, in the other cases I would add only 1 day, for Monday to Thursday.
Maybe there is a better way?
In this image I tried to show how the result of an examplatory query should
look like. I have all dates available but I want to filter for the 14th, which should show me the 11 dates, because of the added workday. Since June 11 is Friday
I think my Main problem is I don't know how make the extra column depending on the given datetime, it should add 3 on Fridays and 1 on every other day (Saturday and Sunday don't exist in the base data)
Thanks for your responses.
I have a solution now. If it can be easier, let me know, if you like. Anyway, thanks for your help everyone
Solution:
DECLARE #Date DATETIME
Set #Date = '14/06/2021'
SELECT
a.DAT,
(CASE WHEN DATENAME(dw, a.DAT) = 'Friday' THEN DATEADD(DAY, 3, a.DAT) ELSE DATEADD(DAY, 1, a.DAT) END) as RealDate
FROM a
WHERE (CASE WHEN DATENAME(dw, #Date) = 'Monday' THEN DATEADD(DAY, -3, #Date) ELSE DATEADD(DAY, -1, #Date) END) = a.DAT
You seem to be describing a case expression:
SELECT (CASE WHEN DATENAME(weekday, date) = 'Friday'
THEN DATEADD(DAY, 3, date)
ELSE DATEADD(DAY, 1, date)
END) as next_date
Welcome to S/O. If your table already has a datetime column, that is probably best. Dont try to break things down such as a date column, a time column, and some workday column. There are plenty of functions that allow you to extract parts as needed. However, your post tags only show SQL instead of a specific database such as sql-server, mysql, oracle, etc. but appears SQL-Server via DateAdd() function.
With date functions you could do things like filtering based on the "Day" of the given date/time column NOT being a Sat or Sun. Or, filtering on a single month by a >= first day of the month but less than first day of following month. This way you dont have to worry about time factors. This type of query would allow inclusion of working up to 11:59:59pm before following day.
If you edit your question with any additional clarifications on why adding 3 days per your example may help offer additional resolution. Try not to just put things that could be long in comments. Then just comment back on my post and I'll try to follow-up (in addition to others that may follow this post).

SQL 'Round' Up a Date to a Given Day of the week

My company groups all tasks into individual weeks that end on a Thursday. Thus a task due on 3/20/19 would be grouped into the 3/21 week and tasks due on 3/22 group into the 3/28/19 week.
I'm looking to calculate this field (called duedate_Weekdue) based on an input duedate.
The following works but doesn't seem like the simplest way to do this. Anyone have more elegant methods?
Select
getdate() as duedate,
datepart(yy,getdate()) as duedate_yr,
datepart(ww,getdate()) as duedate_ww,
CASE
When datename(dw,Dateadd(day,1,getdate()))='Thursday' Then Dateadd(day,1,getdate())
When datename(dw,Dateadd(day,2,getdate()))='Thursday' Then Dateadd(day,2,getdate())
When datename(dw,Dateadd(day,3,getdate()))='Thursday' Then Dateadd(day,3,getdate())
When datename(dw,Dateadd(day,4,getdate()))='Thursday' Then Dateadd(day,4,getdate())
When datename(dw,Dateadd(day,5,getdate()))='Thursday' Then Dateadd(day,5,getdate())
When datename(dw,Dateadd(day,6,getdate()))='Thursday' Then Dateadd(day,6,getdate())
When datename(dw,Dateadd(day,0,getdate()))='Thursday' Then Dateadd(day,0,getdate())
END as duedate_Weekdue;
You can reduce that to one line of code that uses a little math, and some SQL Engine trivia.
The answers that depend on DATEPART return non-deterministic results, depending on the setting for DATEFIRST, which tells the SQL Engine what day of the week to treat as the first day of the week.
There's a way to do what you want without the risk of getting the wrong result based on a change to the DATEFIRST setting.
Inside SQL Server, day number 0 is January 1, 1900, which happens to have been a Monday. We've all used this little trick to strip the time off of GETDATE() by calculating the number of days since day 0 then adding that number to day 0 to get today's date at midnight:
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()),0)
Similarly, day number 3 was January 4, 1900. That's relevant because that day was a Thursday. Applying a little math to the number of days, and relying on the DATEDIFF function to drop fractions, which it does, this calculation will always return the next Thursday for you:
SELECT DATEADD(DAY, (DATEDIFF(DAY, 3, GETDATE())/7)*7 + 7,3);
Credit to this answer for the assist.
So your final query comes down to this:
Select
getdate() as duedate,
datepart(yy,getdate()) as duedate_yr,
datepart(ww,getdate()) as duedate_ww,
DATEADD(DAY, (DATEDIFF(DAY, 3, GETDATE())/7)*7 + 7,3) as duedate_Weekdue;
If the first day of the week is Sunday, by using the modulo operator %:
cast(dateadd(day, (13 - datepart(dw, getdate())) % 7, getdate()) as date) as duedate_Weekdue
I also applied the casting of the result to date.
Try identifying number of day in week with DATEPART and then adding enough days to go to next thursday:
declare #dt date = '2019-03-22'
declare #weekDay int
SELECT #weekDay = DATEPART(dw, #dt)
if #weekDay <= 5
select DATEADD(day, 5 - #weekDay ,#dt)
else
select DATEADD(day, 12 - #weekDay ,#dt)

Get the actual month difference between two date

I working with strange queries in SQL, I want to find the actual month difference between two dates, for example
StartDate = '1-1-2013'
EndDate = '4-30-2013'
Here I want result to be 4 not 3
select datediff(mm, '1-1-2013', '4-30-2013')
This query will provide me result 3 but actual result is 4. Can anyone help me on this?
Not quite sure if this helps but here's my stab at it:
declare
#date1 datetime = '2013-01-01',
#date2 datetime = '2013-04-30'
select Cast(Round(Cast(DATEDIFF(DD,#date1, #date2) as decimal(5,2)) / 30, 1) as int) as Months
What you're asking isn't as simple as it first appears. One obvious way would be to add one day to your end date before doing the datediff. This would then be doing 1 Jan to 1 May which would be reported as four months.
However you need to consider if there's any tolerances; for example, should 1 Jan to 29 Apr be considered four months, or is that still three months? Obviously this will depend heavily on the context in which you are using these dates.
Please try to add 01-01-2013 and 04-30-2013 instead of what you added here
So query will be as below
select datediff(mm, '1-1-2013', '4-30-2013')
Thanks
Vasanthan
As others have pointed out, the result is absolutely correct: 3 months and 29 days.
You can of course check if the date in the end date is the last date of the month and make an adjustment to produce the desired result:
DECLARE #START DATETIME, #END DATETIME
SELECT #START = '2014-01-01', #END = '2014-04-30'
SELECT #START, #END
SELECT DATEDIFF(MONTH, #START, CASE WHEN MONTH(DATEADD(DAY, 1, #END)) > MONTH(#END) THEN DATEADD(DAY, +1, #END) ELSE #END END)
Not sure about the performance of said example (you're free to experiment with similar ideas), but what it does is add the last missing day to produce the 4 month difference whenever the end date represents the last day of that month.
Now, I agree with the others here that this is a weird thing to do, but if this is what your case requires, then so be it. We can't really comment on your business / programming specific needs here. :)

Find the next occurance of a day of the week in SQL

I'm trying to update a SQL report sproc's WHERE clause to check whether a given date falls on or before the next occurrence of a class. Classes have a StartDate and occur once per week on the same day each week. Given the StartDate, how can I find the next occurrence of that day of the week?
E.G. If the StartDate is 1/18/2012, a Wednesday, and I run the report as of today, 1/26/2012, I need to find 2/1/2012 which is the next Wednesday after 1/26. If the StartDate is 1/19, a Thurs, and I run the report today, the formula should give me Thurs 1/26 which is today.
Here's sort of the idea in SQL:
SELECT *
FROM tbl_Class cs
INNER JOIN tbl_Enrollment sce ON cs.pk_ClassID = sce.fk_ClassID
WHERE ...
AND sce.StartDate < [Find date of next class after #AsOfDate using cs.StartDate]
Here's some example SQL that I came up with. 3 iterations so you can follow how I got to the end. The 3rd iteration should be something you can incorporate into a WHERE clause by substituting your column names for the variables.
Setup:
DECLARE #Startdate DATETIME,#currentdate datetime
SET #Startdate = '1-26-2012'
SET #Currentdate = '1-23-2012'
--This section just normalizes it so you can use 7 as the interval
--The offset depends on your current setting for DATEFIRST, U.S. English default is 7, Sunday.
-- see http://msdn.microsoft.com/en-us/library/ms187766.aspx
DECLARE #StartDateWorkingDayOfWeek int,#CurrentDateWorkingDayOfWeek int
SELECT #StartDateWorkingDayOfWeek =(DATEPART(weekday,#Startdate)-2)
SELECT #CurrentDateWorkingDayOfWeek=(DATEPART(weekday,#Currentdate)-2)
Iteration #1
--Iteration 1
IF #StartDateWorkingDayOfWeek < #CurrentDateWorkingDayOfWeek
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 + 7,#StartDateWorkingDayOfWeek)
else
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 + 0,#StartDateWorkingDayOfWeek)
Iteration #2
--Iteration 2
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 +
CASE WHEN #StartDateWorkingDayOfWeek < #CurrentDateWorkingDayOfWeek
then 7
ELSE 0
end
,#StartDateWorkingDayOfWeek)
Iteration #3
--iteration 3
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 +
CASE WHEN (DATEPART(weekday,#Startdate)-2) < (DATEPART(weekday,#Currentdate)-2)
then 7
ELSE 0
end
,(DATEPART(weekday,#Startdate)-2))
Hat tip to this article:
http://www.sqlmag.com/article/tsql3/datetime-calculations-part-3
Here's what I came up with thanks to TetonSig and his reference to this link: http://www.sqlmag.com/article/tsql3/datetime-calculations-part-3
We can get the date of the previous Monday exclusive of the current date (#AsOfDate) like so:
SELECT DATEADD(day, DATEDIFF(day,0, #AsOfDate-1) /7*7, 0);
This gets the number of days between 1/1/1900 and #AsOfDate in days. /7*7 converts that to whole weeks, and then adds it back to 1/1/1900 (a Mon) to get the Monday before #AsOfDate. The -1 makes it exclusive of #AsOfDate. Without the minus 1, if #AsOfDate were on a Monday, it would be counted as the "previous" Monday.
Next the author shows that to get the inclusive next Monday, we simply need to add 7 to the exclusive previous Monday formula:
SELECT DATEADD(d, DATEDIFF(day,0, #AsOfDate-1) /7*7, 0)+7;
Voila! We've now got the first Monday on or after #AsOfDate. The only problem is, the Monday (0) above is a moving target in my case. I need the first [DayOfWeek] determined by the class date, not the first Monday. I need to swap out a ClassDayOfWeek calculation for the 0s above:
DATEADD(d, DATEDIFF(d, [ClassDayOfWeek], #AsOfDate-1)/7*7, [ClassDayOfWeek])+7
I wanted to calculate the ClassDayOfWeek without being dependent on or having to mess with setting ##datefirst. So I calculated it relative to the base date:
DATEDIFF(d, 0, StartDate)%7
This gives 0 for Mon, 6 for Sun so we can now plug that in for [ClassDayOfWeek]. I should point out that this 0-6 value is dates 1/1/1900-1/7/1900 represented as an int.
DATEADD(d, DATEDIFF(d, DATEDIFF(d, 0, StartDate)%7, #AsOfDate-1)/7*7, DATEDIFF(d, 0, StartDate)%7)+7
And in use per the question:
SELECT *
FROM tbl_Class cs
INNER JOIN tbl_Enrollment sce ON cs.pk_ClassID = sce.fk_ClassID
WHERE ...
AND sce.StartDate < DATEADD(d,
DATEDIFF(d,
DATEDIFF(d, 0, cs.StartDate)%7,
#AsOfDate-1)/7*7,
DATEDIFF(d, 0, cs.StartDate)%7)+7
I derived the answer with a simple case statement.
In your situation #targetDOW would be the day of the week of the class.
DECLARE #todayDOW INT = DATEPART(dw, GETDATE());
DECLARE #diff INT = (#targetDOW - #todayDOW);
SELECT
CASE
WHEN #diff = 0 THEN GETDATE()
WHEN #diff > 0 THEN DATEADD(d,#diff,GETDATE())
WHEN #diff < 0 THEN DATEADD(d,#diff + 7,GETDATE())
END;

Sliding Week Comparison

I want to compare data from the current week (1 through 52) to the data from two weeks prior. I need to make sure that when the new-year boundary is crossed, the comparison keeps working. I have a working predicate, like this:
WHERE ( DATEPART(ww,MA.DateOpen) + 1 ) % 52 = DATEPART(ww,#ImportDate) - 1
The above logic works, but it does not account for proximate years. In other words, unwanted data from week 'X' of all prior years will be included in the comparison. So the predicate must be expanded to account for years, which in turn requires handling the new year boundary: week 1 of 2012 should be compared against week 51 of 2011.
How can this be done without a verbose looking predicate?
update:
This question is complicated by the need for "week of year" to be treated as Tuesday through Tuesday. My current "correct" attempt is embodied in the [VerboseCheck] case below.
SET DATEFIRST 2
DECLARE #ImportDate date = '2011-1-4'
DECLARE #DateOpen date = '2010-12-20'
select #ImportDate,
YEAR(#DateOpen),
DATEPART(ww, #DateOpen),
YEAR(DATEADD(dd,-14,#ImportDate)),
DATEPART(ww,DATEADD(dd,-14,#ImportDate)),
CASE WHEN
YEAR(#DateOpen) + DATEPART(ww, #DateOpen) =
YEAR(DATEADD(dd,-14,#ImportDate)) + DATEPART(ww,DATEADD(dd,-14,#ImportDate))
THEN 1 ELSE 0 END [VerboseCheck],
CASE WHEN DATEDIFF (ww, #DateOpen, #ImportDate) =2
THEN 1 ELSE 0 END [SimpleCheck]
The [SimpleCheck] would be perfect, but it gives a different answer than [VerboseCheck]. If I advance #DateOpen one more day, then the two give the same answer. This suggests that both are not honoring the DATEFIRST evaluation in the same way.
Seems like DateDiff would be best used here. However as you noted DATEFIRST is indeed not respected.
So we just add Itzik Ben-Gan's solution and we get
WHERE DATEDIFF (ww,
DATEADD( day, -##DATEFIRST , #DateOpen),
DATEADD( day, -##DATEFIRST , #ImportDate)
) =2
why not just DATEADD('dd', -14, DateOpen)?
Would something like the following work to compare the years?
WHERE ( DATEPART(ww,MA.DateOpen) + 1 ) % 52 = DATEPART(ww,#ImportDate) - 1
AND YEAR(MA.DateOpen)=YEAR(DATEADD(d, -14, #ImportDate))