Related
Suppose now we are in September, I want output of the last Saturday date in the previous month, August, where 28-08-2021 falls under last Saturday of previous month in SQL Server
..fiddle..
select *, datename(weekday, pmlsat), dateadd(week, 1, pmlsat)
from
(
select _date,
--last saturday of previous month
dateadd(day, -datepart(weekday, dateadd(day, ##datefirst, eomonth(_date, -1)))%7, eomonth(_date, -1)) as pmlsat
from
(
select top(100) dateadd(month, row_number() over(order by ##spid), '20141215') as _date
from sys.all_objects
) as d
) as p
order by _date;
DECLARE #date1 DATETIME
SET #date1='2021-8-31'
WHILE Day(#date1) >= 1
BEGIN
IF (SELECT Datename(weekday, #date1)) = 'Saturday'
BREAK
SET #date1=Dateadd(dd, -1, #date1)
CONTINUE
END
SELECT Datename(weekday, #date1) AS 'Datename',
(SELECT CONVERT(NVARCHAR(20), #date1, 23)) AS 'DATE'
First, let's talk about how to get the beginning of this month. There are a multiple ways, I find DATEFROMPARTS() the most intuitive (see Simplify Date Period Calculations in SQL Server):
DECLARE #FirstOfMonth date = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
SELECT #FirstOfMonth;
-- result:
-- 2021-09-01
Now, the last Saturday in the previous month must be between 1 and 7 days before the first of this month. So we can generate a sequence of 7 consecutive numbers, and subtract those days from the first of the month, like this:
DECLARE #FirstOfMonth date = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
SELECT #FirstOfMonth;
;WITH n(n) AS
(
SELECT 1 UNION ALL
SELECT n + 1 FROM n WHERE n < 7
)
SELECT d = DATEADD(DAY, -n, #FirstOfMonth) FROM n;
/* result:
2021-08-31
2021-08-30
2021-08-29
2021-08-28
2021-08-27
2021-08-26
2021-08-25 */
To determine what a Saturday is, you either need to rely on DATEPART(WEEKDAY, date) - which in turn is affected by SET DATEFIRST, or you need to rely on DATENAME(WEEKDAY, date) - which in turn is affected by SET LANGUAGE. I will err toward language being more stable (English), so:
DECLARE #FirstOfMonth date = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
SELECT #FirstOfMonth;
;WITH n(n) AS
(
SELECT 1 UNION ALL
SELECT n + 1 FROM n WHERE n < 7
),
d(d) AS
(
SELECT DATEADD(DAY, -n, #FirstOfMonth)
FROM n
)
SELECT LastMonthLastSaturday = d
FROM d
WHERE DATENAME(WEEKDAY, d) = 'Saturday';
-- result:
-- 2021-08-28
But that is a subjective call - if you can't rely on one of those, get a calendar table, then it's simply something like:
SELECT LastMonthLastSaturday = MAX(TheDate)
FROM dbo.Calendar
WHERE TheDayOfWeekName = 'Saturday'
AND TheDate < DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
How can I get the date of specific day ? Like if I have Thursday or month number ?
If I give 12 for instance I want to get the date of 12th day of this month. Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
DATEFROMPARTS function can construct a date from day, month and year.
DATEPARTS does the opposite - gives you the day, month, year, hour, etc. of a date. Or you can use functions like YEAR, MONTH and DAY.
You can deconstruct the value returned by GETDATE function and construct whatever date you want. Here is for example how to get the date for 12th day of the current month:
select DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 12)
Converting 'Sun' or 'Sat' to date is a bit more difficult. First, they aren't quite deterministic. If today is Friday, "Sunday this week" means "next Sunday" in some parts of the world and "last Sunday" in others. You should implement your own logic based on the value returned by DATEPART(dw, GETDATE()) (which will give you the day of the week).
To find the weekday of the current month
DECLARE #daynumber INT = 12
SELECT datename(weekday, dateadd(d, #daynumber - 1, getdate()))
To find the dates of the current month of a given weekday
DECLARE #dayname char(3) = 'sat'
;WITH CTE as
(
SELECt TOP
(datediff(D, eomonth(getdate(), -1),eomonth(getdate())))
dateadd(d,row_number()over(ORDER BY 1/0),
eomonth(getdate(),-1))date
FROM
(values(1),(2),(3),(4),(5),(6))x(x),
(values(1),(2),(3),(4),(5),(6))y(x)
)
SELECT day(date) monthday, date
FROM CTE
WHERE left(datename(weekday, date),3) = #dayname
select sysdatetime(); --2018-12-13 16:29:56.0560574
---If I give 12 for instance I want to get the date of 12th day of this month.
declare #numDate int = 12;
select dateadd(m, datediff(m,0,getdate()),#numDate - 1 ); --2018-12-12 00:00:00.000
--Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
declare #text nvarchar(20) = 'Sunday';
declare #dateStart date = dateadd(month, datediff(month, 0, sysdatetime()), 0),
#days int =( select (DAY(dateadd(dd,-1,DATEADD(m,1,cast(2018 as varchar(4)) + '-' + cast(12 as varchar(2)) +'-01')))));
declare #dateEnd date = DATEADD(day,#days-1,#dateStart);
;WITH CTE (Dates,EndDate) AS
(
SELECT #dateStart AS Dates,#dateEnd AS EndDate
UNION ALL
SELECT DATEADD(day,1,Dates),EndDate
FROM CTE
WHERE DATEADD(day,1,Dates) <= EndDate
)
SELECT CTE.Dates, DATENAME(DW, CTE.Dates)
FROM CTE
where DATENAME(DW, CTE.Dates) = #text;
Result:
Dates,Day
2018/12/2,Sunday
2018/12/9,Sunday
2018/12/16,Sunday
2018/12/23,Sunday
2018/12/30,Sunday
-- Here is how to get week day name to week day number
DECLARE #T TABLE (Dow INT, NameOfDay VARCHAR(15), ShortName CHAR(3));
WITH Days AS
(
SELECT TOP 7
ROW_NUMBER() OVER(PARTITION BY object_id ORDER BY object_id) AS RowNo
FROM
sys.all_columns
)
INSERT INTO #T
SELECT
RowNo,
DATENAME(WEEKDAY, RowNo - 1),
LEFT(DATENAME(WEEKDAY, RowNo - 1), 3)
FROM
Days
SELECT
*
FROM
#T;
-- Here is how to get start of period
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0) AS StartOfDay,
DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek,
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) AS StartOfMonth,
DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS StartOfYear;
-- An example
WITH
StartPeriods AS
(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek
),
SelectedDay AS
(
SELECT
Dow - 1 AS Dow,
(SELECT StartOfWeek FROM StartPeriods) AS StartOfWeek
FROM
#T
WHERE
ShortName = 'Wed'
)
SELECT
DATEADD(DAY, Dow, StartOfWeek)
FROM
SelectedDay;
I didn't think this code would work, and now that is seems to be working I'm worried that it might not always work:
IF OBJECT_ID('tempdb..#Dates') IS NOT NULL DROP TABLE #Dates
; WITH Dates
AS (
SELECT CAST(DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0) AS DATE) AS CDate
UNION ALL
SELECT DATEADD(dd, 1, CDate) AS CDate
FROM Dates
WHERE DATEADD(dd, 1, CDate) < CAST(GETDATE() AS DATE)
)
SELECT d.CDate
, d.CDate AS BDate
INTO #Dates
FROM Dates d
OPTION (MAXRECURSION 400)
; WITH BDate
AS (
SELECT CDate
FROM #Dates d
WHERE CDate NOT IN ('2018-01-01', '2018-01-15', '2018-02-19') -- New Years, MLK Day, Presidents Day
AND DATEPART(dw, d.CDate) NOT IN (1,7)
)
UPDATE d
SET d.BDate = b.CDate
FROM #Dates d
JOIN BDate b
ON d.CDate <= b.CDate
SELECT * FROM #Dates
What I don't understand is how it knows which value to choose for the UPDATE statement. If we choose a random date like January 24th, a SELECT statement would give us multiple values (since that's a Wednesday and we're avoiding weekends, that list would include January 24th, 25th and 26th, and then jump to 29th, 30th, 31st, etc.). So why is it choosing the one I actually want (the minimum value that meets the criteria)? It won't let me use ORDER BY to force the order, of course.
Normally I would write my code more like this:
IF OBJECT_ID('tempdb..#Dates') IS NOT NULL DROP TABLE #Dates
; WITH Dates
AS (
SELECT CAST(DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0) AS DATE) AS CDate
UNION ALL
SELECT DATEADD(dd, 1, CDate) AS CDate
FROM Dates
WHERE DATEADD(dd, 1, CDate) < CAST(GETDATE() AS DATE)
)
SELECT d.CDate
, d.CDate AS BDate
INTO #Dates
FROM Dates d
OPTION (MAXRECURSION 400)
; WITH BDate
AS (
SELECT CDate
FROM #Dates d
WHERE CDate NOT IN ('2018-01-01', '2018-01-15', '2018-02-19') -- New Years, MLK Day, Presidents Day
AND DATEPART(dw, d.CDate) NOT IN (1,7)
)
, BDMin
AS (
SELECT d.CDate
, MIN(b.CDate) AS BDate
FROM #Dates d
JOIN BDate b
ON d.CDate <= b.CDate
GROUP BY d.CDate
)
UPDATE d
SET d.BDate = b.BDate
FROM #Dates d
JOIN BDMin b
ON d.CDate = b.CDate
SELECT * FROM #Dates
It certainly seems safer, but now I'm wondering if it's necessary.
Can I find the date of a day that is on which dates the Saturdays and Sundays of a specific month fall? For e.g consider the month of JANUARY-2017. The following dates are weekend days:
7/1/2017 - Saturday
14/1/2017 - Saturday
21/1/2017 - Saturday
28/1/2017 - Saturday
1/1/2017 - Sunday
8/1/2017 - Sunday
15/1/2017 - Sunday
22/1/2017 - Sunday
29/1/2017 - Sunday
I want a SQL Server query for this such that when I pass in month and year as input, I should get back all the above dates (only dates of Saturday and Sunday) as output
I do not wish to use any user defined function and want to finish it in a single SELECT statement
Note: As already noted by another user in the comments, this query depends upon your server settings, namely DATEFIRST. If you need alterations to the query because of different settings, just tell me and I can change it around for you.
Using a CTE as dummy data...
/* Ignore this part...*/
WITH CTE AS
(
SELECT CAST('01/01/2017' AS DATE) AS [Date]
UNION ALL
SELECT DATEADD(DAY,1,[Date])
FROM CTE
WHERE DATE <= '12/31/2017'
)
/*Your actual SELECT statement would look like this, from your own table of course*/
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM CTE
WHERE DATEPART(dw,[Date]) IN (1,7)
AND MONTH([Date]) = 12--<month>
AND YEAR([Date]) = 2017--<year>
OPTION (MAXRECURSION 0) -- You won't need this line if you're querying a real table
;
If running that works for you, then your real query would probably look something like this:
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM < the table you want >
WHERE DATEPART(dw,[Date]) IN (1,7) -- Only Sundays and Saturdays
AND MONTH([Date]) = < the month you want >
AND YEAR([Date]) = < the year you want >
;
If you want to generate the data, then a CTE is the way to go. If you're passing parameters, it would look something like this:
DECLARE
#MONTH INT
,#YEAR INT
;
SET #MONTH = 1;
SET #YEAR = 2017;
WITH CTE AS
(
SELECT CAST(CAST(#MONTH AS VARCHAR(2)) + '/01/' + CAST(#YEAR AS VARCHAR(4)) AS [Date]) AS DATE
UNION ALL
SELECT DATEADD(DAY,1,[Date])
FROM CTE
WHERE DATE <= CAST(#MONTH AS VARCHAR(2)) +
CASE
WHEN #MONTH IN (9,4,6,11)
THEN '/30/'
WHEN #MONTH IN (1,3,5,7,8,10,12)
THEN '/31/'
WHEN #MONTH = 2 AND #YEAR/4.00 = #YEAR/4
THEN '/29/'
ELSE '/28/'
END
+ CAST(#YEAR AS VARCHAR(4))
)
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM CTE
WHERE DATEPART(dw,[Date]) IN (1,7)
OPTION (MAXRECURSION 0)
;
Please try this one.
DECLARE #Year AS INT=2017,
#Month AS INT=3,
#FirstDateOfYear DATETIME,
#LastDateOfYear DATETIME
SELECT #FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
SELECT #LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0)
-- Creating Query to Prepare Year Data
;WITH cte AS (
SELECT 1 AS DayID,
#FirstDateOfYear AS FromDate,
DATENAME(dw, #FirstDateOfYear) AS Dayname
UNION ALL
SELECT cte.DayID + 1 AS DayID,
DATEADD(d, 1 ,cte.FromDate),
DATENAME(dw, DATEADD(d, 1 ,cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d,1,cte.FromDate) < #LastDateOfYear
)
SELECT FromDate AS Date, Dayname
FROM CTE
WHERE DayName IN ('Saturday','Sunday') and month(FromDate) = #Month
OPTION (MaxRecursion 370)
This should do the trick:
DECLARE #month date = '2017-01-01'
SET #month = dateadd(month, datediff(month, 0, #month), 0)
;WITH CTE as
(
SELECT 0 x
FROM (values(1),(1),(1),(1),(1),(1)) x(n)
),
CTE2 as
(
SELECT
top(day(eomonth(#month)))
-- use this syntax for sqlserver 2008
-- top(datediff(d, #month,dateadd(month,1,#month)))
cast(dateadd(d, row_number()over(order by(select 1))-1,#month) as date) cDate
FROM CTE CROSS JOIN CTE C2
)
SELECT
cDate,
datename(weekday, cDate) Weekday
FROM CTE2
WHERE
datediff(d,0,cDate)%7 > 4
Fiddle
From https://www.sqlservercentral.com/articles/finding-the-correct-weekday-regardless-of-datefirst, you simply:
(DATEPART(dw, #your_date) + ##DATEFIRST) % 7 NOT BETWEEN 2 AND 6
As requested, single select, language neutral, dateFirst neutral, almost SQL version neutral:
declare #OneDate datetime = '28/01/2017'; -- Any date from the target month/year
select MyDate -- raw date or ...
-- convert(varchar, MyDate, 103) + ' - ' + dateName(dw, MyDate) -- as Sample
as WeekEndDate
from (
select dateAdd(dd, number, dateAdd(mm, dateDiff(mm, 0, #OneDate), 0)) as MyDate
from master..spt_values
where type = 'P' and number < 31
) j
where 1 + (datePart(dw, MyDate) + ##DATEFIRST + 5) % 7 in (6, 7)
and month(MyDate) = month(#OneDate)
-- order by 1 + (datePart(dw, MyDate) + ##DATEFIRST + 5) % 7, MyDate -- as Sample
;
Another way to solve this problem as follow -
DECLARE #MONTH INT,#YEAR INT
SET #MONTH = 1;
SET #YEAR = 2017;
Declare #StartDate date =CAST(CAST(#MONTH AS VARCHAR(2)) + '/01/' + CAST(#YEAR AS VARCHAR(4)) AS [Date]), #EndDate date
Set #EndDate = EOMONTH(#StartDate)
Declare #Temp table (DateOfDay date, DaysName varchar(50))
While(#StartDate <= #EndDate)
Begin
Insert into #Temp
SELECT #StartDate DateOfMonth,
case when DATENAME(DW, #StartDate) = 'Saturday' then DATENAME(DW, #StartDate)
when DATENAME(DW, #StartDate) = 'Sunday' then DATENAME(DW, #StartDate)
end DaysName
set #StartDate = DATEADD(d,1,#StartDate)
End
select * from #Temp where DaysName is not null order by DaysName, DateOfDay
Can't you do something like this?
SELECT DATENAME(dw,'10/11/2016') AS DATE
WHERE DATE CONTAINS('Saturday') OR DATE CONTAINS('SUNDAY')
and instead of '10/11/2016' you only have to figure out how to generate all the dates in a month/year?
I need to get last day of all previous months including current month, upto a specified month. For example, I need last days of september, aug, july, june, may, april, march, feb, jan, dec 2015 like so:
temptable_mytable:
last_day_of_month
-----------------
2016-09-30
2016-08-31
2016-07-31
2016-06-30
2016-05-31
2016-04-30
2016-03-31
2016-02-30
2016-01-31
2015-12-31
I need to specify the month and year to go back to - in above case it's December 2015, but it could also be September 2015 and such. Is there a way that I can do a loop and do this instead of having to calculate separately for each month end?
Use a recursive CTE with the EOMONTH function.
DECLARE #startdate DATE = '2016-01-01'
;WITH CTE
AS
(
SELECT EOMONTH(GETDATE()) as 'Dates'
UNION ALL
SELECT EOMONTH(DATEADD(MONTH, -1, [Dates]))
FROM CTE WHERE Dates > DATEADD(MONTH, 1, #startdate)
)
SELECT * FROM CTE
with temp as (select -1 i union all
select i+1 i from temp where i < 8)
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+i*-1,0)) from temp
declare #LASTMONTH date = '2018-10-01';
WITH MTHS AS (
SELECT dateadd(month,month(getdate()),dateadd(year,year(getdate()) - 1900, 0)) aday
UNION ALL
SELECT DATEADD(month,1,aday) from MTHS WHERE aday <= #LASTMONTH
),
LASTDAYS AS (SELECT DATEADD(day,-1,aday) finaldayofmonth from MTHS)
select * from LASTDAYS
Here is a version that goes forward or backwards as appropriate
declare #LASTMONTH date = '2013-10-01';
WITH DIF AS (SELECT CASE WHEN
YEAR(#LASTMONTH) * 12 + MONTH(#LASTMONTH)
>= YEAR(GETDATE()) * 12 + MONTH(getdate()) THEN 1 ELSE -1 END x),
MTHS AS (
SELECT dateadd(month,month(getdate()),dateadd(year,year(getdate()) - 1900, 0)) aday
UNION ALL
SELECT DATEADD(month,(SELECT X from dif),aday) from MTHS
WHERE month(aday) != month(dateadd(month,1,#LASTMONTH)) or YEAR(aday) != YEAR(dateadd(month,1,#LASTMONTH))
),
LASTDAYS AS (SELECT DATEADD(day,-1,aday) finaldayofmonth from MTHS)
select * from LASTDAYS order by finaldayofmonth
Here's one approach, using a CTE to generate a list of incrementing numbers to allow us to then have something to select from and use in a DATEADD to go back for the appropriate number of months.
Typically, if you're doing this quite frequently, instead of generating numbers on the fly like this with the CROSS JOIN, I'd recommend just creating a "Numbers" table that just holds numbers from 1 to "some number high enough to meet your needs"
DECLARE #Date DATE = '20151201'
DECLARE #MonthsBackToGo INTEGER
SELECT #MonthsBackToGo = DATEDIFF(mm, #Date, GETDATE()) + 1;
WITH _Numbers AS
(
SELECT TOP (#MonthsBackToGo) ROW_NUMBER() OVER (ORDER BY o.object_id) AS Number
FROM sys.objects o
CROSS JOIN sys.objects o2
)
SELECT EOMONTH(DATEADD(mm, -(Number- 1), GETDATE())) AS last_day_of_month
FROM _Numbers
This should scale out no matter how far you go back or forward for your originating table or object.
SET NOCOUNT ON;
DECLARE #Dates TABLE ( dt DATE)
DECLARE #Start DATE = DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
DECLARE #End DATE = DATEADD(YEAR, 1, #Start)
WHILE #Start <= #End
BEGIN
INSERT INTO #Dates (dt) VALUES (#Start)
SELECT #Start = DATEADD(DAY, 1, #Start)
END
; With x as
(
Select
dt
, ROW_NUMBER() OVER(PARTITION BY DATEPART(YEAR, Dt), DATEPART(MONTH, Dt) ORDER BY Dt Desc) AS rwn
From #Dates
)
Select *
From x
WHERE rwn = 1
ORDER BY Dt
This was cribbed together quick based on a couple different SO answers for the parts:
DECLARE #startdate datetime, #enddate datetime
set #startdate = '2015-12-01'
set #enddate = getdate()
;WITH T(date)
AS
(
SELECT #startdate
UNION ALL
SELECT DateAdd(day,1,T.date) FROM T WHERE T.date < #enddate
)
SELECT DISTINCT
DATEADD(
day,
-1,
CAST(CAST(YEAR(date) AS varchar) + '-' + CAST(MONTH(date)AS varchar) + '-01' AS DATETIME))
FROM T OPTION (MAXRECURSION 32767);