The problem is your WHERE
clause:
SELECT DATEADD(yy, 1,Start_Date), End_Date FROM cte
WHERE Start_Date < End_Date -- PROBLEM IS HERE
It looks like the two rows that come out of your anchor query are:
Start_Date End_Date
--------------------------
2019-06-07 2019-08-16
2019-07-25 2019-08-16
Then you plug these two rows into the recursive member, and since the condition start_date < End_date
is met, you generate two more rows, with a year added to the start date. I think you need to check whether the start date plus one year is after the end date, e.g.
;WITH cte(Start_Date, End_Date)
AS
(
SELECT CAST(StartDateUtc as Date),
CAST(CASE WHEN BusinessGoal.EndDateUtc is NULL THEN GETDATE() else cast(BusinessGoal.EndDateUtc as Date) end as Date) FROM BusinessGoal
WHERE BusinessGoal.Period = 'Year'
UNION ALL
SELECT DATEADD(yy, 1,Start_Date), End_Date FROM cte
WHERE DATEADD(yy, 1,Start_Date) < End_Date -- check the start date plus one year
)
SELECT * FROM cte
As an aside, although it won't make much of a difference for a small data set, using a recursive CTE to generate series in this way is one of the worst ways of doing it. There are much better ways described here: Generate a set or series without loops - part 1. Also, this query would become much easier if you had a Calendar table.
SELECT StartDate = c.Date,
EndDate = CAST(ISNULL(bg.EndDateUtc, GETDATE()) AS DATE)
FROM BusinessGoal AS bg
INNER JOIN dbo.Calendar AS c
ON c.Date >= bg.StartDateUtc
AND c.CalendarDay = DATEPART(DAY, bg.StartDateUtc)
AND c.CalendarMonth = DATEPART(MONTH, bg.StartDateUtc)
AND c.Date <= ISNULL(bg.EndDateUtc, GETDATE())
WHERE bg.Period = 'Year';
If you don't have a calendar table, you can also do this with a numbers table created on the fly. e.g.
-- Generate a list of numbers from 0-999
WITH Numbers (Number) AS
( SELECT ROW_NUMBER() OVER(ORDER BY n1.N) - 1
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n1 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n2 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n3 (N)
),
-- Dummy Data
BusinessGoal AS
( SELECT x.ID,
StartDateUtc = CONVERT(DATE, x.StartDateUtc),
EndDateUtc = CONVERT(DATE, x.EndDateUtc),
Period = 'Year'
FROM (VALUES
(1, '20190607', '20190816'),
(2, '20190725', '20190816'),
(3, '20180725', NULL)
) X (ID, StartDateUtc, EndDateUtc)
)
SELECT ID,
StartDate = DATEADD(YEAR, n.Number, bg.StartDateUtc),
EndDate = CAST(ISNULL(bg.EndDateUtc, GETDATE()) AS DATE)
FROM BusinessGoal AS bg
INNER JOIN Numbers AS n
ON DATEADD(YEAR, n.Number, bg.StartDateUtc) < ISNULL(bg.EndDateUtc, GETDATE());
While this looks much more complicated, most of the query is in generating sample data, if you had a permanent numbers table (or view) the query is as simple as:
SELECT StartDate = DATEADD(YEAR, n.Number, bg.StartDateUtc),
EndDate = CAST(ISNULL(bg.EndDateUtc, GETDATE()) AS DATE)
FROM BusinessGoal AS bg
INNER JOIN Numbers AS n
ON DATEADD(YEAR, n.Number, bg.StartDateUtc) < ISNULL(bg.EndDateUtc, GETDATE());
And will perform much better than a recursive CTE on larger data sets, or as you get dates that are further apart.