A solution consist in building a calendar with a recusive CTE query, and join this calendar to the incident table and perform a GROUP BY.
Here is a complete sample with data generation (at the end):
DECLARE @startDate date = DATEFROMPARTS(2021, 3, 3);
DECLARE @endDate date = DATEFROMPARTS(2021, 12, 30);
WITH dateRange AS
(
SELECT 1 AS MonthNumber,
@startDate AS MonthStartDate,
IIF(DATEADD(DAY, 29, @startDate) > @endDate, @endDate, DATEADD(DAY, 29, @startDate)) AS MonthEndDate
UNION ALL
SELECT 1 + dra.MonthNumber,
DATEADD(DAY, 1, dra.MonthEndDate),
IIF(DATEADD(DAY, 29, DATEADD(DAY, 1, dra.MonthEndDate)) > @endDate, @endDate, DATEADD(DAY, 29, DATEADD(DAY, 1, dra.MonthEndDate))) AS MonthEndDate
FROM dateRange dra
WHERE dra.MonthEndDate < @endDate AND dra.MonthNumber < 100
)
SELECT dra.MonthNumber,
dra.MonthStartDate,
dra.MonthEndDate,
inc.IncidentType,
COUNT(1) AS IncidentCount
FROM #incident inc
INNER JOIN dateRange dra ON inc.IncidentDate BETWEEN dra.MonthStartDate AND dra.MonthEndDate
GROUP BY dra.MonthNumber, dra.MonthStartDate, dra.MonthEndDate, inc.IncidentType;
Data generation:
CREATE TABLE #incident
(
IncidentDate date NOT NULL,
IncidentType varchar(50) NOT NULL
);
INSERT INTO #incident
VALUES (DATEFROMPARTS(2021, 3, 3), 'Breakdown'),
(DATEFROMPARTS(2021, 3, 4), 'Crash'),
(DATEFROMPARTS(2021, 3, 5), 'Fire'),
(DATEFROMPARTS(2021, 3, 6), 'Breakdown'),
(DATEFROMPARTS(2021, 3, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 3, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 3, 8), 'Crash'),
(DATEFROMPARTS(2021, 3, 9), 'Fire'),
(DATEFROMPARTS(2021, 5, 3), 'Breakdown'),
(DATEFROMPARTS(2021, 5, 4), 'Crash'),
(DATEFROMPARTS(2021, 5, 5), 'Fire'),
(DATEFROMPARTS(2021, 5, 6), 'Breakdown'),
(DATEFROMPARTS(2021, 5, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 5, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 5, 8), 'Crash'),
(DATEFROMPARTS(2021, 5, 9), 'Fire'),
(DATEFROMPARTS(2021, 7, 3), 'Breakdown'),
(DATEFROMPARTS(2021, 7, 4), 'Crash'),
(DATEFROMPARTS(2021, 7, 5), 'Fire'),
(DATEFROMPARTS(2021, 7, 6), 'Breakdown'),
(DATEFROMPARTS(2021, 7, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 7, 7), 'Breakdown'),
(DATEFROMPARTS(2021, 7, 8), 'Crash'),
(DATEFROMPARTS(2021, 7, 9), 'Fire');
Isolated calendar creation:
DECLARE @startDate date = DATEFROMPARTS(2021, 3, 3);
DECLARE @endDate date = DATEFROMPARTS(2021, 12, 30);
WITH dateRange AS
(
SELECT 1 AS MonthNumber,
@startDate AS MonthStartDate,
IIF(DATEADD(DAY, 29, @startDate) > @endDate, @endDate, DATEADD(DAY, 29, @startDate)) AS MonthEndDate
UNION ALL
SELECT 1 + dr.MonthNumber,
DATEADD(DAY, 1, dr.MonthEndDate),
IIF(DATEADD(DAY, 29, DATEADD(DAY, 1, dr.MonthEndDate)) > @endDate, @endDate, DATEADD(DAY, 29, DATEADD(DAY, 1, dr.MonthEndDate))) AS MonthEndDate
FROM dateRange dr
WHERE dr.MonthEndDate < @endDate AND dr.MonthNumber < 100
)
SELECT MonthNumber,
MonthStartDate,
MonthEndDate
FROM dateRange;