Jeroen Mostert's solution in your comments is the best possible way to go. David's solution will get you what you are looking for but his function is a Multi-statement
inline table valued function, you want and inline
table valued function for the reasons outlined here. Even if though you are only dealing with a small handful of rows, Multi-statement can poison the performance of queries that use call them.
To create an inline
table valued function (iTVF) you just need to understand how tally tables work. Doing so will change your career. The iTVF version of what you are looking for looks like this:
CREATE FUNCTION dbo.MonthYearRange (@startdate DATE, @enddate DATE)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH L1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) x(N)),
iTally(N) AS
( SELECT 0 UNION ALL
SELECT TOP (DATEDIFF(MONTH,@startdate,@enddate)) ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM L1 a CROSS JOIN L1 b CROSS JOIN L1 c)
SELECT [Month] = MONTH(d.dt),
[Year] = YEAR(d.dt)
FROM iTally i
CROSS APPLY (VALUES (DATEADD(MONTH,i.N,@startdate))) d(dt);
This query:
DECLARE @startdate DATE = '2018-05-01',
@enddate DATE = '2019-02-28';
Returns:
Month Year
----------- -----------
5 2018
6 2018
7 2018
8 2018
9 2018
10 2018
11 2018
12 2018
1 2019
2 2019