How to use fiscal calendar in a query - sql

I have two tables "Fiscal_calendar" and "Sales". The 2 tables do not link together. I want to be able to write a query that calculates the total sales made a in a week according to the fiscal calendar. Is this possible? Our fiscal calendar starts from December 1st and every month end falls on a friday.
Any help would be appreciated.
Fiscal_calendar
Period Period1_StartDate Period1_EndDate Period2_StartDate Period2_EndDate...........
2018 01/12/2017 29/12/2017 30/01/2018 26/01/2018
Sales
Sales_order_no Amount Date Customer
111 20453 03/12/2017 abc
112 23154 04/12/2017 bbb
113 20201 10/12/2017 ddd
114 39012 11/12/2017 ccc
115 11111 18/12/2017 eee
116 22222 25/12/2017 uuu
So there are 4 weeks between Period 1 startdate and enddate. And the first 2 sales fall under week 1. So total sales for Week 1 would be 43607
OUTPUT
WEEK Total_Sales
W1 43607
W2 59213
W3 11111
W4 22222

Two uses of CROSS APPLY to fix (normalise) the fiscal calendar table in to something useful, then a simple GROUP BY.
The WHERE clause picks the row from the fiscal calendar, that the period or week (or whatever) from the derived views.
The LEFT JOIN is in case of no sales in that week.
The ON clause looks at the fiscal calendar AND the week derived view, just in case the derived view describes a week that's not really in that year.
SELECT
p.year_id,
p.period_id,
w.week_id,
SUM(s.amount) AS total_amount
FROM
fiscal_calendar c
CROSS APPLY
(
SELECT period, 1, period1_startDate, period1_endDate
UNION ALL SELECT period, 2, period2_startDate, period2_endDate
..
UNION ALL SELECT period, 13, period13_startDate, period13_endDate
)
AS p(year_id, period_id, startDate, endDate)
CROSS APPLY
(
SELECT 1, DATEADD(d, 0, startDate), DATEADD(d, 6, startDate)
UNION ALL SELECT 2, DATEADD(d, 7, startDate), DATEADD(d, 13, startDate)
UNION ALL SELECT 3, DATEADD(d, 14, startDate), DATEADD(d, 20, startDate)
UNION ALL SELECT 4, DATEADD(d, 21, startDate), DATEADD(d, 27, startDate)
)
AS w(week_id, startDate, endDate)
LEFT JOIN
sales s
ON s.date BETWEEN c.period1_startdate AND c.period13_enddate
AND s.date BETWEEN w.startDate AND w.endDate
WHERE
p.year_id = 2018
AND p.period_id = 1
GROUP BY
p.year_id,
p.period_id,
w.week_id

UNPIVOT is a useful TSQL operator to use when normalizing data. Basically it takes a row and makes new rows - one for each column specified in a list of columns.
In the case below I unpivot on the PeriodX_StartDate columns. I originally did two unpivots, the other on PeriodX_EndDate. The end date was unnecessary, but in case you're wondering how I joined the two unpivots: I used a cross apply to generate a key for each week and had a predicate that tested if they were equal. Without the predicate you get a Cartesian product.
To generate the weeks I used a CROSS APPLY that is very similar to the one in #MatBailie's answer, except I avoided the unions by generating a set of arguments for each week that are passed into functions that generate the dates.
WITH normalizedCal AS (
SELECT [Period], MonthStart, WeekKey, StartDate, CutoffDate
FROM fiscal_calendar cal
UNPIVOT (
MonthStart FOR StartKey IN (Period1_StartDate, Period2_StartDate, ..., PeriodN_StartDate)
) AS startInfo
CROSS APPLY (
SELECT 'WK' + CAST((StartIndex / 7) + 1 AS char(1)) [WeekKey]
, DATEADD(day, startIndex, MonthStart) [StartDate]
, DATEADD(day, EndIndex, MonthStart) [CutoffDate]
FROM (
VALUES ( 0, 7 ), ( 7, 14 ), ( 14, 21 ), ( 21, 28 )
) rangeValues ( StartIndex, EndIndex )
) weekInfo
)
SELECT [Period], MonthStart, WeekKey, COALESCE(SUM(AMOUNT), 0) [Total_Sales]
FROM normalizedCal nc
LEFT JOIN sales ON sales.Date >= nc.StartDate AND sales.Date < nc.CutoffDate
GROUP BY [Period], MonthStart, WeekKey
ORDER BY [Period], MonthStart, WeekKey
Working Example with sample data:
WITH normalizedCal AS (
SELECT [Period], MonthStart, WeekKey, StartDate, CutoffDate
FROM (
VALUES (2018, '2017-12-01', '2017-12-29', '2017-12-30', '2018-01-26')
) cal ([Period], Period1_StartDate, Period1_EndDate, Period2_StartDate, Period2_EndDate)
UNPIVOT (
MonthStart FOR StartKey IN (Period1_StartDate, Period2_StartDate)
) AS startInfo
CROSS APPLY (
SELECT 'WK' + CAST((StartIndex / 7) + 1 AS char(1)) [WeekKey]
, DATEADD(day, startIndex, MonthStart) [StartDate]
, DATEADD(day, EndIndex, MonthStart) [CutoffDate]
FROM (
VALUES ( 0, 7 ), ( 7, 14 ), ( 14, 21 ), ( 21, 28 )
) rangeValues ( StartIndex, EndIndex )
) weekInfo
),
sales AS (
SELECT [Sales_order_no],[Amount], CAST([Date] as DATE) [Date],[Customer]
FROM (VALUES
(111, 20453, '2017-12-03', N'abc'),
(112, 23154, '2017-12-04', N'bbb'),
(113, 20201, '2017-12-10', N'ddd'),
(114, 39012, '2017-12-11', N'ccc'),
(115, 11111, '2017-12-18', N'eee'),
(116, 22222, '2017-12-25', N'uuu')
) [salessrc] ( [Sales_order_no],[Amount],[Date],[Customer])
)
SELECT [Period], MonthStart, WeekKey, COALESCE(SUM(AMOUNT), 0) [Total_Sales]
FROM normalizedCal nc
LEFT JOIN sales ON sales.Date >= nc.StartDate AND sales.Date < nc.CutoffDate
GROUP BY [Period], MonthStart, WeekKey
ORDER BY [Period], MonthStart, WeekKey

according to your provided example, You could do something like this :
SELECT *
FROM (
SELECT
[YEAR] = YEAR([Date]),
[MONTH] = MONTH([Date]),
[WEEK] = DATEPART(week, [Date]) - DATEPART(week, DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)),
SUM(Amount) OVER(PARTITION BY YEAR([Date]), DATEPART(week, [Date]) - DATEPART(week, DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0))+ 1) AS TotalSales
FROM Sales
)D
GROUP BY
[YEAR],
[MONTH],
[WEEK],
TotalSales
this will give you the same results that you've provided in your example, however, since we don't know how your Fiscal_calendar is configured you will need to see is it a mandatory to link it with it or not.

Related

Merging two SELECT queries with same date fields

I have a table of Tasks where I have records for a particular date. I want to have all dates in one month displayed with numbers of tasks per date. If on some date there were no record of a task it should be written 0.
I have results with duplicating records from the same date when there were tasks on a given day.
Table:
Date Tasks
2021-08-01 0
2021-08-02 0
2021-08-03 0
2021-08-03 25
2021-08-04 0
2021-08-04 18
2021-08-05 0
2021-08-05 31
2021-08-06 0
SQL code I am using:
Declare #year int = 2021, #month int = 8;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value +1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year, #month, 1)))
)
SELECT datefromparts(#year, #month, numbers.value) AS 'Datum', 0 AS 'Tasks' FROM numbers
UNION
SELECT CONVERT(date, added_d) AS 'Datum', COUNT(*) AS 'Tasks' FROM Crm.Task
WHERE YEAR(added_d) = '2021' AND MONTH(added_d) = '8' GROUP BY CONVERT(date, added_d)
How can I remove duplicates that I will have only one date record 21-08-03 with 25 tasks?
Thank you for your help
You requires OUTER JOIN :
WITH numbers as (
Select datefromparts(#year, #month, 1) as value
UNION ALL
Select DATEADD(DAY, 1, value) as value
from numbers
where value < EOMONTH(value)
)
select num.value, COUNT(tsk.added_d) AS Tasks
from numbers num left join
Crm.Task tsk
on CONVERT(date, tsk.added_d) = num.value
GROUP BY num.value;
If you want all dates for one month, you can do:
with dates as (
select datefromparts(#year, #month, 1) as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < eomonth(dte)
)
You can then incorporate this into the logic using an outer join or subquery:
with dates as (
select datefromparts(#year, #month, 1) as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < eomonth(dte)
)
select d.dte, count(t.added_d)
from dates d left join
Crm.Task t
on convert(date, t.added_d) = d.dte
group by d.dte
order by d.dte;
You can easily extend the logic for the CTE for more than one month, by adjusting the where clause in the second clause.

Calendar Table in SQL Server

I am trying to create a Calendar Table with following columns and requirements. I have this calendar table made previously and I am not able to update it to my current requirements.
Date
Week No
Week Name
Week Start Date
Week End Date
Month
Month Start Date
Month End Date
List item
Quarter
Quarter Start Date
Quarter End Date
Year
Requirements
--Week Starts from Sunday and ends at Saturday
--Week Name should be in format 31Aug--to--06Sep
--Month should be ending date of Week's Month
--Calendar should start from 2015-08-31
--Financial Year should be in format of FY16-17
--Year should start from 1-Jun Till 31 May
--Quarter should be of 3 months of above mentioned dates
--Week No should be incremental number should not get reset after year completion.
The Query
DECLARE #StartDate date = '20150831'
DECLARE #CutoffDate date = '20300101'
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM seq
),
src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = Concat('Q',DATEPART(Quarter, d)),
Financial_Year = DATEPART(YEAR, d),
Financial_Quarter=Datepart(QUARTER,d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheFirstOfFYear = DATEFROMPARTS(YEAR(d), 4, 1),
TheFirstOfYear = DATEFROMPARTS(YEAR(d), 1, 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
),
Dimension AS
(
SELECT
TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
-- TheDayOfWeekInMonth = CONVERT(tinyint, ROW_NUMBER() OVER
-- (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)),
TheDayOfYear,
TheWeek,
TheFirstOfWeek = DATEADD(DAY, 1 - TheDayOfWeek, TheDate),
TheLastOfWeek = DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)),
TheMonth,
TheMonthName,
TheFirstOfMonth,
TheLastOfMonth = MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth),
TheFirstOfNextMonth = DATEADD(MONTH, 1, TheFirstOfMonth),
TheLastOfNextMonth = DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)),
TheQuarter,
TheFirstOfQuarter = MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheLastOfQuarter = MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheYear,
TheFirstOfYear = DATEFROMPARTS(TheYear, 1, 1),
TheFirstOfFYear = DATEFROMPARTS(TheYear, 4, 1),
TheLastOfYear,
MMYYYY = CONVERT(char(2), CONVERT(char(8), TheDate, 101))
+ CONVERT(char(4), TheYear),
Financial_Quarter = Datepart(Quarter,DATEADD(MONTH, -3, TheFirstOfMonth)), /*Starting Financial Quarter from April*/
Financial_Year =CASE
WHEN Financial_Quarter = 1 THEN DATEPART(Year,Dateadd(Year,-1,TheFirstofYear)) ELSE THEYEAR END
FROM src
)
SELECT * FROM Dimension
ORDER BY TheDate
OPTION (MAXRECURSION 0);
How to convert Months depending on the weeks for example a month starts on Sunday (week also starts from Sunday) date be 01-MM-YYYY and the month should always end on Saturday giving 4 weeks normally in a month. The month cannot start or end with dates of previous week or month it should always have only the whole weeks, starting from Sunday and ending on Saturday.
As I mention in the comments, seems you just need a windowed COUNT. This is a guess, based on a lack of expected results, but this should get you on the right path. I also use the same set based method I used for your colleague's question:
DECLARE #StartDate date = '20150831'
DECLARE #CutoffDate date = '20300101';
/*
; is a terminator, not a "beginingator". It goes at the end of ALL your statements,
not at the start of statements that require the PREVIOUS statement to be properly terminated.
*/
WITH N AS
(SELECT N
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS
(SELECT 0 AS I
UNION ALL
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1,N N2,N N3, N N4), --Up to 1000 rows. Add more cross joins for more rows
D AS
(SELECT DATEADD(DAY, T.I, #StartDate) AS d
FROM Tally T),
Src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(SELECT CONVERT(date, d) AS TheDate,
DATEPART(DAY, d) AS TheDay,
DATENAME(WEEKDAY, d) AS TheDayName,
DATEPART(WEEK, d) AS TheWeek,
DATEPART(WEEKDAY, d) AS TheDayOfWeek,
DATEPART(MONTH, d) AS TheMonth,
DATENAME(MONTH, d) AS TheMonthName,
CONCAT('Q', DATEPART(QUARTER, d)) AS TheQuarter,
DATEPART(YEAR, d) AS Financial_Year,
DATEPART(QUARTER, d) AS Financial_Quarter,
DATEPART(YEAR, d) AS TheYear,
DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS TheFirstOfMonth,
DATEFROMPARTS(YEAR(d), 4, 1) AS TheFirstOfFYear,
DATEFROMPARTS(YEAR(d), 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(YEAR(d), 12, 31) AS TheLastOfYear,
DATEPART(DAYOFYEAR, d) AS TheDayOfYear
FROM d),
Dimension AS
(SELECT TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
CONVERT(tinyint, ROW_NUMBER() OVER (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)) AS TheDayOfWeekInMonth,
TheDayOfYear,
TheWeek,
DATEADD(DAY, 1 - TheDayOfWeek, TheDate) AS TheFirstOfWeek,
DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)) AS TheLastOfWeek,
CONVERT(tinyint, DENSE_RANK() OVER (PARTITION BY TheYear, TheMonth ORDER BY TheWeek)) AS TheWeekOfMonth,
TheMonth,
TheMonthName,
TheFirstOfMonth,
MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth) AS TheLastOfMonth,
DATEADD(MONTH, 1, TheFirstOfMonth) AS TheFirstOfNextMonth,
DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)) AS TheLastOfNextMonth,
TheQuarter,
MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheFirstOfQuarter,
MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheLastOfQuarter,
TheYear,
DATEFROMPARTS(TheYear, 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(TheYear, 4, 1) AS TheFirstOfFYear,
TheLastOfYear,
CONVERT(char(2), CONVERT(char(8), TheDate, 101)) + CONVERT(char(4), TheYear) AS MMYYYY,
DATEPART(QUARTER, DATEADD(MONTH, -3, TheFirstOfMonth)) AS Financial_Quarter, /*Starting Financial Quarter from April*/
CASE
WHEN Financial_Quarter = 1 THEN DATEPART(YEAR, DATEADD(YEAR, -1, TheFirstOfYear))
ELSE TheYear
END AS Financial_Year,
COUNT(CASE WHEN DATENAME(WEEKDAY,TheDate) = 'Sunday' THEN 1 END) OVER (PARTITION BY YEAR(TheDate), MONTH(TheMonth) ORDER BY TheDate) AS TheWeekNo
FROM src)
SELECT *
FROM Dimension
ORDER BY TheDate;
Here is a single SELECT query that uses the WITH RECURSIVE statement. It does not require loops procedures etc.
A RECURSIVE SQL common table expression (CTE) is a query that continuously references a previous result until it returns an empty result. It's achieved using a CTE, which in SQL is known as a “WITH” statement.
In the query below, the second date_range with statement has a UNION ALL. The first query in that statement is the first record of the table, and the subsequent records are all from the UNION ALL second statement where it continuously references itself until it reaches the end_date, as expresses as "WHERE next_date <= end_date"
WITH RECURSIVE min_max_dates AS (
SELECT
MIN(start_at) start_date,
MAX(end_at) end_date
FROM table_or_hard_code ay
),
date_range AS (
select
start_date date,
DATE_TRUNC('month', start_date) month_date,
MONTH(start_date) as month_num,
YEAR(start_date) as month_year,
TO_CHAR((start_date),'MMMM') as month_display,
MONTHNAME (start_date) as month_short_display,
CONCAT(TO_CHAR((start_date),'MMMM'), ', ', YEAR(start_date)) as month_year_display,
DATEADD(DAY, 1, start_date) as next_date,
start_date,
end_date
FROM min_max_dates
UNION ALL
SELECT
dr.next_date date,
DATE_TRUNC('month', dr.next_date) month_date,
MONTH(dr.next_date) as month_num,
YEAR(dr.next_date) as month_year,
TO_CHAR((dr.next_date),'MMMM') as month_display,
MONTHNAME (dr.next_date) as month_short_display,
CONCAT(TO_CHAR((dr.next_date),'MMMM'), ', ', YEAR(dr.next_date)) as month_year_display,
DATEADD(DAY, 1, dr.next_date) as next_date,
start_date,
end_date
FROM date_range dr
WHERE next_date <= end_date
)
SELECT date, month_date, month_year, month_num, month_display, month_short_display, month_year_display
FROM date_range

Get total sales of last 12 months even if values are null

I have following code in which it presently gives month wise Total Sales for current year, I need to get total sales from last month of previous year to current month of this year.
My query is as follows:
;WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE DATEPART(MONTH,MONTH_NAME) < 12),octe AS(
SELECT (DATENAME (MONTH, DATEADD ( MONTH, DATEPART(MONTH, OI.CreatedDate), -1) )) AS MONTH_NAME,
SUM (OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM Order_Item OI
GROUP BY MONTH(OI.CreatedDate))
SELECT DATENAME(MONTH,m.MONTH_NAME) + '' + DATENAME(YEAR,m.MONTH_NAME) as
MONTH_NAME, o.TOTAL_SALES FROM mcte m LEFT JOIN octe o ON o.MONTH_NAME = DATENAME(MONTH,m.MONTH_NAME)
and I am getting records
MONTH_NAME TOTAL_SALES
July2019 54023.45
August2019 NULL
December2019 NULL
September2019 NULL
October2019 NULL
November2019 NULL
Here I am only getting data for previous year only, not getting data for current year.Can anyone please guide me on this.
Thank you
You are only generating months up to 12. Try replacing the first CTE with:
WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE month_name < GETDATE()
),
Note the difference is the WHERE clause.
The entire query should look like this:
WITH months AS (
SELECT DATEFROMPARTS(YEAR(getdate()) - 1, MONTH(getdate()), 1) as month
UNION ALL
SELECT DATEADD(MONTH, 1, month)
FROM months
WHERE EOMONTH(month) < GETDATE()
)
SELECT m.month, SUM(OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM months m LEFT JOIN
Order_Item OI oi
ON oi.CreatedDate >= m.month AND
oi.CreatedDate < DATEAADD(month, 1, m.month)
GROUP BY m.month
Try doing this:
DECLARE #CurDate DATE = GET_DATE()
DECLARE #OneYearPrior DATE = DATEADD(YEAR, -1, #CurDate)
WITH relevant_months(start_date, month_of_sale, year_of_sale) AS (
SELECT
#CurDate AS start_date,
MONTH(#CurDate) as month_of_sale,
YEAR(#CurDate) as year_of_sale
UNION ALL
SELECT DATEADD(MONTH, -1, start_date) AS start_date,
MONTH(DATEADD(MONTH, -1, start_date)) as month_of_sale,
YEAR(DATEADD(MONTH, -1, start_date)) AS year_of_sale
FROM relevant_months
WHERE DATEADD(MONTH, -1, start_date) >= #OneYearPrior
),
relevant_data AS (
SELECT OI.CreatedDate,
OI.ItemQty,
OI.TotalPrice,
MONTH(OI.CreatedDate), AS month_of_sale,
YEAR(OI.CreatedDate) AS year_of_sale
FROM Order_Item OI
WHERE OI.CreatedDate >= DATEADD(YEAR, -1, GETDATE())
)
SELECT rm.month_of_sale as month, rm.year_of_sale as year,
SUM(rd.ItemQty*rd.TotalPrice) as total_sales
FROM relevant_months rm
LEFT JOIN relevant_data rd
ON rm.month_of_sale = rd.month_of_sale
AND rm.year_of_sale = rd.year_of_sale
GROUP BY rm.month_of_sale, rm.year_of_sale
ORDER BY rm.year_of_sale asc, rm.month_of_sale asc

How to include month names with count as 0 for monthly data count?

I am running the below query to fetch monthly data count for last year till current month.
SELECT dateName(month,tn.processstarttime) as reqMonth, count(ID) as requestCount, year(tn.processstarttime) as reqYear
FROM table A tn WHERE year(tn.processstarttime) in (year(DATEADD(MONTH, 0, GETDATE())),year(DATEADD(MONTH, -12, GETDATE())))
AND tn.processstarttime>DATEADD(MONTH, -12, GETDATE())
GROUP BY dateName(month,tn.processstarttime),year(tn.processstarttime)
order by dateName(month,tn.processstarttime),year(tn.processstarttime)
But this query is not giving month names for which the data count is 0.
Please support to include months for which the data count is 0 with value as 0.
Thanks
The standard way is to use calendar table with all years nad month required and then LEFT JOIN to it your result. When there will be no corresponding record in your table to some meonth, you will use COALESCE to obtain 0 for those months. See below query (I used CTEs to get calendar table, IMO the easiest way):
;with MonthNames as (
select 1 MonthNo, 'January' MonthName
union all
select 2, 'February'
union all
select 3, 'March'
union all
select 4, 'April'
union all
select 5, 'May'
union all
select 6, 'June'
union all
select 7, 'July'
union all
select 8, 'August'
union all
select 9, 'September'
union all
select 10, 'October'
union all
select 11, 'November'
union all
select 12, 'December'
), Years as (
select 2017 Year union all select 2018 union all select 2019
), CalendarTable as (
select * from MonthNames cross join Years
)
select ct.MonthName,
ct.Year,
COALESCE(t.requestCount, 0) requestCount
from CalendarTable ct
left join (YOUR WHOLE SELECT) t
on t.Year = ct.Year and t.month = ct.MonthNo
This answer can be similar to Michal Turczyn's, but there are a couple of substantial differences:
Do not pay much attention on the differences creating the first two CTEs, as different they look as irrelevant, simply matter of styles.
The important difference is in the third CTE and the way of filter your query, the name of your column (processstarttime) is giving a clue that it can be a very large table, so if you use where clauses using functions for the selected table columns, it will work, but your query won't be indexed and the performance can be a further issue
Not top relevant but also important is that It cover the "monthly data count for last year till current month" PO's requirement without hardcoding dates, it can be within a view or function that doesn't need to be modifiied year by year...
WITH months AS (
SELECT 1 AS MonthNum, DATENAME(Month,DATEFROMPARTS(1,1,1)) AS MonthName
UNION ALL
SELECT MonthNum + 1, DATENAME(Month,DATEFROMPARTS(1, MonthNum + 1, 1)) AS MonthName
FROM months
WHERE MonthNum <= 11
),
years as (
SELECT YEAR(GETDATE())-1 AS Year
UNION ALL
SELECT Year + 1
FROM years
WHERE Year + 1 <= YEAR(GETDATE())
),
dates as (
SELECT Year, MonthNum, MonthName, DATEFROMPARTS(Year, MonthNum, 1) AS DateStart, DATEADD(MONTH, 1, DATEFROMPARTS(Year, MonthNum, 1)) AS DateEnd
FROM years
CROSS JOIN months
)
SELECT D.Year, D.MonthNum, D.MonthName, COUNT(ID) AS RequesCount
FROM dates D
LEFT JOIN YourTable A ON A.ProcessStartTime >= DateStart AND A.ProcessStartTime < DateEnd
WHERE DateStart < GETDATE()
GROUP BY D.Year, D.MonthNum, D.MonthName
ORDER BY Year, MonthNum
Try this
SELECT months.month_name AS reqMonth, COUNT(ID) AS requestCount, YEAR(tn.processstarttime) AS reqYear
FROM A tn
RIGHT JOIN (VALUES ('january'),('february'),('march'),('april'),
('may'),('june'),('july'),('august'),('september'),
('october'),('november'),('december')) as months(month_name)
ON DATENAME(month,tn.processstarttime) = months.month_name
AND YEAR(tn.processstarttime) in (YEAR(DATEADD(MONTH, 0, GETDATE())),year(DATEADD(MONTH, -12, GETDATE())))
AND tn.processstarttime>DATEADD(MONTH, -12, GETDATE())
GROUP BY months.month_name,YEAR(tn.processstarttime)
order by months.month_name,YEAR(tn.processstarttime)
In action here

How can I select the 1st of every month for the last 5 years in sql?

I am trying to get a list of the 1st of the Month for the last 5 years. How can i do that ?? I have a select statement:
select convert(varchar(10), dateadd(mm,Datediff(mm,0,getdate()),0),111) as StartDate
but i am not sure how to get a list for every month.
with dates
as (
select dateadd(month, datediff(month, 0, getdate()), 0) as date
union all
select dateadd(month, - 1, date)
from dates
)
select top 60 *
from dates
with cte as (
select DATEFROMPARTS ( datepart(yyyy,getdate()), datepart(mm,getdate()), 1 ) as startdate
union all
select dateadd(month,-1,startdate) from dates
where datediff(year,startdate,getdate()) <> 5 )
select CONVERT ( varchar(12), startdate , 107 ) from cte;