How do i get the date for last friday of the month in T-SQL?
I will be passing the year and month as parameter,e.g, 201211.
If I pass '201211' as parameter it should return me '20121130' as answer as it's the date of last friday of month of november'12.
The 5 January 1900 was a Friday. This uses that a base date and calculates the last Friday in any given month though you must give it a date during the month rather than just the month itself. Replace the 2012-12-01 in this with a date in your month
SELECT DATEADD(DY,DATEDIFF(DY,'1900-01-05',DATEADD(MM,DATEDIFF(MM,0,'2012-12-01'),30))/7*7,'1900-01-05')
You can also use this to get the last Saturday by replacing the 1900-01-05 WITH 1900-01-06 etc.
This would be much simpler using a calendar table; after creating the appropriate columns for your own needs you can just write this:
select
max([Date])
from
dbo.Calendar
where
YearAndMonth = 201211 and
DayOfWeek = 'Friday'
A calendar table is generally a much better solution for determining dates than using functions because the code is much more readable and you can use your own definition of things like WeekNumber, FinancialQuarter etc. that vary widely between countries and even companies.
I created a scalar function for this:
create function [dbo].[lastDWMonth]
(
#y int
,#m int
,#dw int
)
returns date
as
begin
declare #d date
;with x as
(
select datefromparts(#y,#m,1) d
union all
select dateadd(day,1,d) from x where d < eomonth(datefromparts(#y,#m,1))
)
select
#d = max(d)
from
x
where
datepart(dw,d) = #dw
return #d
end
Declare #d1 datetime = '2019-12-23'
Declare #searchDay int = 2 -- monday
select DATEADD(DAY, #searchDay-DATEPART(WEEKday, DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0))),DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0)))
This will give you Date on last Monday of the month, you can change your search by changing value in #searchDay
Related
I am trying to select records from today and the same day of each week for the last 4 weeks.
Today (Tuesday)
Last Tuesday
The Tuesday before that
The Tuesday before that
I need this to be tied to current date because I am going to run this query every day so I don't want to use a between or something where I manually specify the date range.
Everything I have found or tried so far has pulled the last month of data but not the last 4 weeks of the same weekday.
select *
from table
where thedatecolumn >= DATEADD(mm, -1, GETDATE())
This works but pulls everything from the last month.
If today's date is 7/10/2019 I need
Data from 7/10/2019
Data from 7/3/2019
Data from 6/26/2019
Data from 6/19/2019
Every day I will run this query, so I need it to be dynamic based on the current date.
I believe you want to look back 21 days and then filter those dates that have the same day of week:
select * from table
where thedatecolumn >= DATEADD(DAY, -21, CAST(GETDATE() AS DATE))
and DATEPART(WEEKDAY, thedatecolumn) = DATEPART(WEEKDAY, GETDATE())
You Can try using a recursive cte which starts today and repeatedly substracts 7 days - so you ensure you always land on the same weekday. Following an example:
WITH cteFromToday AS(
SELECT 0 AS WeeksBack, GETDATE() AS MyDate
UNION ALL
SELECT WeeksBack + 1 AS WeeksBack, DATEADD(d, -7, MyDate) AS MyDate
FROM cteFromToday
)
SELECT TOP 5 *
FROM cteFromToday
OPTION ( MaxRecursion 0 );
This is quite simple. Substitute CURRENT_TIMESTAMP here for any given date.
SELECT CONVERT(DATE,CURRENT_TIMESTAMP) AS Today,
DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AS LastWeek ,
DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AS TwoWeeksAgo,
DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AS ThreeWeeksAgo
SO, if you want to get data for a set of ranges for one entire day with those dates:
SELECT something
WHERE
datetimecolumn >= CONVERT(DATE,CURRENT_TIMESTAMP) AND datetimecolumn < DATEADD(DAY,1, CONVERT(DATE,CURRENT_TIMESTAMP)) -- Todays range,
OR datetimecolumn >= DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)))-- LastWeek ,
OR datetimecolumn >= DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)))-- TwoWeeksAgo,
OR datetimecolumn >= DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1, DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP))) -- ThreeWeeksAgo
Spent the better half of the day trying to figure this one out.
I want to get the datediff of two dates, based on their ISO week.
Here is my code:
SET DATEFIRST 1 ;
DECLARE #A date, #B date;
SET #A = '20180829'; -- August 29th
SET #B = '20180902'; -- September 2nd
SELECT DATEDIFF(WW, #A, #B )
If you check: http://whatweekisit.org/ (Week 35, Year 2018) you can see that it runs August 27 to September 2.
The code above will return a DateDiff = 1, which should be 0. Trying to run DateDiff on ISO week just returns the following error:
The datepart iso_week is not supported by date function datediff
I've tried taking out the week dateparts from the dates, but then I get the problem when comparing dates from different years.
Is there a way around this?
So after some reading, there's not a native way to do it. But here's a workaround:
DECLARE #A DATE = '20180829' -- August 29th
DECLARE #B DATE = '20180902' -- September 2nd
--We need to back each date up to the first day of its week.
DECLARE #A_FIRSTWEEKDAY DATE = DATEADD(DAY, -(DATEPART(WEEKDAY, #A) - 1), #A)
DECLARE #B_FIRSTWEEKDAY DATE = DATEADD(DAY, -(DATEPART(WEEKDAY, #B) - 1), #B)
/*The WEEKDAY part counts Sunday as day 1. If the original date was
a Sunday, it backed up zero days and it still needs to back up six
days. If it was any other day, it backed all the way up to Sunday
and now it needs to move forward one day.*/
IF DATEPART(WEEKDAY, #A) = 1
BEGIN SET #A_FIRSTWEEKDAY = DATEADD(DAY, -6, #A_FIRSTWEEKDAY) END
ELSE BEGIN SET #A_FIRSTWEEKDAY = DATEADD(DAY, 1, #A_FIRSTWEEKDAY) END
IF DATEPART(WEEKDAY, #B) = 1
BEGIN SET #B_FIRSTWEEKDAY = DATEADD(DAY, -6, #B_FIRSTWEEKDAY) END
ELSE BEGIN SET #B_FIRSTWEEKDAY = DATEADD(DAY, 1, #B_FIRSTWEEKDAY) END
--Now we can just difference the weeks.
SELECT DATEDIFF(DAY, #A_FIRSTWEEKDAY, #B_FIRSTWEEKDAY) / 7
This is because DATEDIFF always uses Sunday as the first day of the week to ensure the function operates in a deterministic way: https://learn.microsoft.com/datediff-transact-sql
So 20180902 (Sunday) is first day of next week.
SELECT DATEDIFF(ww, DATEADD(dd,-1, #A ), DATEADD(dd,-1,#B))
--Seems to do the trick?
Taken from: Number of weeks and partial weeks between two days calculated wrong
Though I cannot see why the other post in the link adds a 1 at the end.
I have a query for calculating first and last date in the week, according to given date. It is enough to set #dDate and the query will calculate first (monday) and last date (sunday) for that week.
Problem is, that is calculating wrong and I don't understand why.
Example:
#dDate = 2019-10-03 (year-month-day).
Result:
W_START W_END
2019-09-25 2019-10-01
But it should be:
2019-09-30 2019-10-06
Why is that?
Query:
set datefirst 1
declare #dDate date = cast('2019-10-16' as date)
select #dDAte
declare #year int = (select DATEPART(year, #dDAte))
select #year
declare #StartingDate date = cast(('' + cast(#year as nvarchar(4)) + '-01-01') as date)
select #StartingDate
declare #dateWeekEnd date = (select DATEADD(week, (datepart(week, cast(#dDate as date)) - 1), #StartingDate))
declare #dateWeekStart date = dateadd(day, -6, #dateWeekEnd)
select #dateWeekStart W_START, #dateWeekEnd W_END
Days of the week are so complicated. I find it easier to remember that 2001-01-01 fell on a Monday.
Then, the following date arithmetic does what you want:
select dateadd(day,
7 * (datediff(day, '2001-01-01', #dDate) / 7),
'2001-01-01' -- 2001-01-01 fell on a Monday
)
I admit this is something of a cop-out/hack. But SQL Server -- and other databases -- make such date arithmetic so cumbersome that simple tricks like this are handy to keep in mind.
I have a table in SQL Server which has many rows, with a created_date column. This column has rows starting from the year 2006.
I want to get all the rows which were created in and before February, 2015. This stored procedure has a parameter #month. It should select all the rows based on the #month value entered.
Here is my query:
select *
from products
where 1=1
and year(created_date) <= 2015
and month(created_date) <= #month
But this query returns only the records which were created in and before February month of previous years excluding records which were created in other months of 2014 (e.g., 2014-03-17, 2014-05-05 are excluded).
I have to get a new date based on the #month entered. Suppose I entered month July, I want to have condition "where created_date < 2015-07-31". So I can do something like this,
So I have changed my query,
declare #date datetime
set #date = CAST((2015 + '-' + #month + '-' + 28) as datetime)
select *
from products
where 1=1
and year(created_date) <= 2015
But this query returns 1905-08-08 00:00:00.000 and I want to get 2015-02-28 00:00:00.000 and also I have to find total number of days based on the #month entered so that I can pass that number to CAST((2015 + '-' + #month + '-' + 28) as datetime) instead of 28.
Just use a single date and specify that the created_date column must be less than that date:
declare #newestDate datetime = '2015-03-01'
select *
from products
where created_date < #newestDate
Note that I set the date to be the 1st March but in the query I use < rather than <=. This will cope with a created_date value including a time component, e.g. 2015-02-28 23:59:59
To generate the value of "February of previous year", you may actually be wanting to use the current month of last year, if so, your date would be:
declare #newestDate datetime =
DATEADD(year, -1, DATEADD(month, DATEDIFF(month, 0, GETDATE())+1, 0))
This would then work next month (i.e. March) and would give your query a rolling month.
Always compare date/time fields to a single value where possible -- this is best for performance as well. You can "round" dates with DATEADD and DATEDIFF.
DECLARE #startOfNextMonth DATETIME;
SELECT #startOfNextMonth = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) + 1, 0);
select * from products where 1=1 and created_date < #startOfNextMonth;
String manipulation to convert dates is also possible, but tends to perform worse and is tricky to get right. This technique applies in general if you want to "round" to years, minutes, 15-second periods, etcetera, which is much harder with strings.
If you can, rewrite your stored procedure to not take a #month parameter but an absolute value that your clients calculate -- it's more general and tends to be easier to work with. Your query then simply reduces to
select * from products where 1=1 and created_date < #limit;
Of course, if you must use a #month, you can construct this offset in the stored procedure itself:
DECLARE #limit DATETIME =
DATEFROMPARTS(DATEPART(YEAR, GETDATE()), #month, 1)
;
This takes advantage of DATEFROMPARTS, which was introduced with SQL Server 2012. For previous versions, reliably constructing a date is considerably messier. There are many wrong ways to do it that will break if the regional settings are set to something unexpected. DATEADD is again of assistance:
DECLARE #limit DATETIME =
DATEADD(MONTH, (DATEPART(YEAR, GETDATE()) - 1900) * 12 + #month - 1, 0)
;
These are not the only methods to construct datetime values, but string manipulation is in any case tricky (because the only reliable format that will not break under regional settings is YYYYMMDD, no dashes).
In this question: Create a date with T-SQL you will see how to construct an sql-server date data type given a certain year and month. Suppose you call that 'my_date'.
You will then be able to do the following:
SELECT * FROM products WHERE created_date < my_date
I am developing a calendaring system whereby events are created. I need to be able to "roll forward" any event (which occurs on a single day) to a user-specified month/year.
For example, March 4, 2013 is a Monday. I need to be able to determine, by the given month/year, what the corresponding date would be - based upon the weekday and its position within the month. So, in this example the corresponding date for April would be April 1, which is a Monday.
Another example: March 13, 2013 is a Wednesday, so the corresponding date in May would be May 8.
If it were not for the fact that user supplied month/year is variable, this would not be so difficult a task; but since it is...
If you had a Dates table containing five columns, FullDate, Month, Day, Year, and DayOfWeek, and populated with dates well into the future you could easily do the following.
Assuming #m and #y are the user-specified month/year to roll forward to, and #d is the event date:
DECLARE #weekNumInMonth int =
(
SELECT COUNT(1)
FROM Dates
WHERE Year = datepart(year #d)
AND Month = datepart(month, #d)
AND DayOfWeek = datepart(weekday, #d)
AND Day <= datepart(day, #d)
)
SELECT MAX(FullDate)
FROM
(
SELECT TOP #weekNumInMonth
FROM Dates
WHERE Year = #y
AND Month = #m
AND DayOfWeek = datepart(weekday, #d)
) x
Without a dates table, you'll just have to do some math:
DECLARE #DOW int = datepart(weekday, #d)
DECLARE #firstDayInMonth date = dateadd(day, 1-datepart(day, #d), #d)
DECLARE #firstDayInMonthDOW int = datepart(weekday, #firstDayInMonth)
DECLARE #firstSameDayInMonth date =
dateadd(day, (7-(#firstDayInMonthDOW-#DOW))%7, #firstDayInMonth)
DECLARE #weekInMonth int = datediff(week, #firstSameDayInMonth, #d)
DECLARE #corr date = datefromparts(#y, #m, 1)
DECLARE #corrDOW int = datepart(weekday, #corr)
DECLARE #corrFirstSameDay date = dateadd(day, (7-(#corrDOW-#DOW))%7, #corr)
SELECT dateadd(week, #weekInMonth, #corrFirstSameDay)
SQL Fiddle example
It's a little ugly, but what it does is:
Get the first day of the month with the same weekday as #d into #firstSameDayInMonth.
Figure out which week # #d is in within its corresponding month, as a 0-based integer #weekInMonth. This is the number of weeks between #firstSameDayInMonth and #d.
Get the first day of month #m, year #y with the same weekday as #d into #corrFirstSameDay.
Add the 0-based number of weeks #weekInMonth to #corrFirstSameDay to get the result.
Can you do it as a one-liner? Sure, just substitute your variables. Be warned though, it's ugly, and there's really nothing to be gained from it except lack of readability IMHO:
SELECT dateadd(week, datediff(week, dateadd(day, (7-(datepart(weekday, dateadd(day,
1-datepart(day, #d), #d))-datepart(weekday, #d)))%7, dateadd(day,
1-datepart(day, #d), #d)), #d), dateadd(day, (7-(datepart(weekday,
datefromparts(#y, #m, 1))-datepart(weekday, #d)))%7, datefromparts(#y, #m, 1)))