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);
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);
So, I am using the following snippet on a procedure, which fills me a temporary table with the first day of each year's week, the week number and month name.
However, when I reach week 53, of 2020, it jumps to week 2 of 2021. This happens because the first of january is in the so called week 53 (which is correct), but it should also be creating another row with the first week of january 2021 (even with the sunday as being in 2020, as it should).
Snippet:
SET DATEFIRST 7
DECLARE #tblSundays TABLE (
[year] INT
,[month] INT
,[week] INT
,[date] DATETIME
)
DECLARE #DateFrom DATETIME = '2020-12-12'
,#DateTo DATETIME = '2021-06-06';
--select #DateFrom,#DateTo;
WITH CTE (dt)
AS (
SELECT #DateFrom
UNION ALL
SELECT DATEADD(d, 1, dt)
FROM CTE
WHERE dt < #DateTo
)
INSERT INTO #tblSundays
SELECT datepart(year, dt)
,datepart(month, dt)
,datepart(week, dt)
,dt
FROM CTE
WHERE datepart("dw", dt) = 1
OPTION (MAXRECURSION 1000)
;
select * from #tblSundays
Is there any way that I can do this within this snippet, or should I create a manual verification?
Thanks!
You should do a couple of little tricks to set week 1 to the first Sunday of the year (tested for the next 30 years):
set datefirst 7
declare #tblSundays table ([year] int, [month] int, [week] int, [date] date)
declare #DateFrom datetime = '20201212', #DateTo datetime = '20510112';
-- Get the next closest Sunday
select #DateFrom = dateAdd(wk, dateDiff(wk, 0, #datefrom - 1 ), 0) + 6
with CTE (dt) as (
select #DateFrom
union all
select dateAdd(dd, 7, dt) from CTE where dt < #DateTo
)
insert into #tblSundays
select datePart(yy, dt),
datePart(mm, dt),
datePart(wk, dt - (1 + (datePart(dy, dt) + 5) % 7) % 7),
dt
from CTE
-- where datepart(dw, dt) = 1
option (maxrecursion 1600);
select *, datePart(wk, [date]) as [standard_wk]
from #tblSundays
I don't see how you could have those two condition on that same snippet, but you could add a second query there to complete what you are trying to do.
Something like this, maybe:
SET datefirst 7
DECLARE #tblSundays TABLE
(
[year] INT,
[month] INT,
[week] INT,
[date] DATETIME
)
DECLARE #DateFrom DATETIME = '2020-12-12',
#DateTo DATETIME = '2021-06-06';
--select #DateFrom,#DateTo;
WITH cte (dt)
AS (SELECT #DateFrom
UNION ALL
SELECT Dateadd(d, 1, dt)
FROM cte
WHERE dt < #DateTo)
INSERT INTO #tblSundays
SELECT Datepart(year, dt),
Datepart(month, dt),
Datepart(week, dt),
dt
FROM cte
WHERE Datepart("dw", dt) = 1
OPTION (maxrecursion 1000);
--second new query
WITH cte (dt)
AS (SELECT #DateFrom
UNION ALL
SELECT Dateadd(d, 1, dt)
FROM cte
WHERE dt < #DateTo)
INSERT INTO #tblSundays
SELECT Datepart(year, dt),
Datepart(month, dt),
Datepart(week, dt),
dt
FROM cte
WHERE Datepart("dw", dt) <> 1
AND Datepart(day, dt) = 1
AND Datepart(week, dt) NOT IN (SELECT week
FROM #tblSundays)
OPTION (maxrecursion 1000);
SELECT *
FROM #tblSundays
ORDER BY year,
month,
week
Would that be OK?
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 have a parameter called Year and you can choose one year at a time. I have a date/time parameter with a calendar called start date and another one called end date.
I would like it to work such that if I pick the year to be 2017 it will show the calendar start and end dates for 2017.
Since my start date and end date parameters are not defaulted from a dataset I am not sure how to control that.
Any help will be appreciated.
You can do it as an expression, but as its easier in SQL you can just create a second dataset with the following:
select convert(date, '1 jan ' + convert(varchar(4), #Year)), dateadd(day, -1, dateadd(year, 1, convert(date, '1 jan ' + convert(varchar(4), #Year))))
I used a recursive CTE common table expression that references itself to solve this. Then you can set the Dataset of the start_date and end_date parameters.
Dataset for the default values of the start and end date parameters
DECLARE #year AS INT
SET #year = 2018
SELECT [start_date] = DATEFROMPARTS(#year, 1, 1), [end_date] = DATEFROMPARTS(#year, 12, 31)
Dataset for the available values of the start and end date parameters
DECLARE #year AS INT
SET #year = 2018
;WITH
source_data_dates
AS
(
SELECT
[date_start] = DATEFROMPARTS(#year, 1, 1)
, [date_end] = DATEFROMPARTS(#year, 12, 31)
)
,
year_date_list([rn], [date_value])
AS
(
SELECT
[rn] = 1
, [date_value] = CAST([date_start] AS DATETIME)
FROM
source_data_dates
UNION ALL
SELECT
[rn] = [rn] + 1
, [date_start] = CAST(DATEADD(DAY, [rn], [date_start]) AS DATETIME)
FROM
year_date_list
, source_data_dates
WHERE
[rn] <= DATEDIFF(DAY, [date_start], [date_end])
)
SELECT
[rn]
, [date_value]
FROM
year_date_list
OPTION (MAXRECURSION 0)
Results:
I think I have a tough one here... :(
I am trying to get an order count by month, even when zero. Here's the problem query:
SELECT datename(month, OrderDate) as Month, COUNT(OrderNumber) AS Orders
FROM OrderTable
WHERE OrderDate >= '2012-01-01' and OrderDate <= '2012-06-30'
GROUP BY year(OrderDate), month(OrderDate), datename(month, OrderDate)
What I'm looking to get is something like this:
Month Orders
----- ------
January 10
February 7
March 0
April 12
May 0
June 5
...but my query skips a row for March and May. I've tried COALESCE(COUNT(OrderNumber), 0) and ISNULL(COUNT(OrderNumber), 0) but I'm pretty sure the grouping is causing that not to work.
This solution doesn't require you to hard-code the list of months you might want, all you need to do is provide any start date and any end date, and it will calculate the month boundaries for you. It includes year in the output so that it will support more than 12 months and so that your start and end dates can cross a year boundary and still order correctly and show the correct month and year.
DECLARE #StartDate SMALLDATETIME, #EndDate SMALLDATETIME;
SELECT #StartDate = '20120101', #EndDate = '20120630';
;WITH d(d) AS
(
SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(MONTH, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
SELECT
[Month] = DATENAME(MONTH, d.d),
[Year] = YEAR(d.d),
OrderCount = COUNT(o.OrderNumber)
FROM d LEFT OUTER JOIN dbo.OrderTable AS o
ON o.OrderDate >= d.d
AND o.OrderDate < DATEADD(MONTH, 1, d.d)
GROUP BY d.d
ORDER BY d.d;
Since your query Just Can't guess the months you want, you will need to have the Months that you want stored in somewhere, Join them with your table, and then group.
Something like:
;With Months (Month)
AS
(
select 'January' as Month
UNION
select 'February' as Month
UNION
select 'March' as Month
UNION
select 'April' as Month
UNION
select 'May' as Month
UNION
select 'June' as Month
UNION
select 'July' as Month
UNION
select 'August' as Month
UNION
select 'September' as Month
UNION
select 'October' as Month
UNION
select 'November' as Month
UNION
select 'December' as Month
)
--Also you could have them in a "Months" Table
Then Just JOIN this table with your table:
Select
SELECT datename(month, OrderDate) as Month, COUNT(OrderNumber)
FROM Months T1
LEFT JOIN OrderTable T2 on datename(month, T2.OrderDate) = T2.Month
WHERE (T2.OrderDate >= '2012-01-01' and T2.OrderDate <= '2012-06-30')
OR T2.OrderDate IS NULL --So will show you the months with no rows
GROUP BY year(T2.OrderDate), month(T2.OrderDate), datename(month, T2.OrderDate)
Hope it works!
Here is one using recursive CTE:
declare #StartDate datetime = '2015-04-01';
declare #EndDate datetime = '2015-06-01';
-- sample data
declare #orders table (OrderNumber int, OrderDate datetime);
insert into #orders
select 11, '2015-04-02'
union all
select 12, '2015-04-03'
union all
select 13, '2015-05-03'
;
-- recursive CTE
with dates
as (
select #StartDate as reportMonth
union all
select dateadd(m, 1, reportMonth)
from dates
where reportMonth < #EndDate
)
select
reportMonth,
Count = count(o.OrderNumber)
from dates
left outer join #orders as o
on o.OrderDate >= reportMonth
and o.OrderDate < dateadd(MONTH, 1, reportMonth)
group by
reportMonth
option (maxrecursion 0);
;