I would not use a recursive CTE here, don't be sucked in by their syntactic sugar, they are little better than a while loop or a cursor, which people are a lot faster to condemn. There are very few scenarios (if any) that do not involve hierarchical data where a recursive CTE is going to be the right solution.
Here, you probably only need a small set of numbers, assuming you won't have more than 100 years worth of data you need 50 rows, which you can get using a couple of simple table valued constructors and cross joining them, finally ROW_NUMBER() will give you your sequence:
SELECT ROW_NUMBER() OVER(ORDER BY N1.N)
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS n1 (N)
CROSS JOIN (VALUES(1),(1),(1),(1),(1)) AS n2 (N);
Now you can join your set of numbers to your table to get your 2 year intervals for the expiry date:
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)) AS n1 (N)
CROSS JOIN (VALUES(1),(1),(1),(1),(1)) AS n2 (N)
)
SELECT am.Customer_No,
am.Opened_Dt,
Expired = DATEADD(YEAR, n.Number * 2, Opened_Dt)
FROM Accounts_Master AS am
CROSS JOIN Numbers AS n;
This will of course give you 50 rows for each record, spanning 100 years after Opened_Dt, which is not required, so you need to filter the results to where the next expiry date is in the future, and the current one is in the past:
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)) AS n1 (N)
CROSS JOIN (VALUES(1),(1),(1),(1),(1)) AS n2 (N)
)
SELECT am.Customer_No,
am.Opened_Dt,
Expired = DATEADD(YEAR, n.Number * 2, Opened_Dt)
FROM Accounts_Master AS am
CROSS JOIN Numbers AS n
WHERE DATEADD(YEAR, n.Number * 2, Opened_Dt) <= GETDATE()
AND DATEADD(YEAR, (n.Number * 2) + 2, Opened_Dt) > GETDATE()
FULL WORKING EXAMPLE
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)) AS n1 (N)
CROSS JOIN (VALUES(1),(1),(1),(1),(1)) AS n2 (N)
)
SELECT am.Customer_No,
am.Opened_Dt,
Expired = DATEADD(YEAR, n.Number * 2, Opened_Dt)
FROM (VALUES
(1, CAST('2010-08-02' AS DATE)),
(2, CAST('2013-04-27' AS DATE)),
(3, CAST('2013-08-24' AS DATE)),
(4, CAST('2015-03-19' AS DATE))
) AS am (Customer_no, Opened_dt)
CROSS JOIN Numbers AS n
WHERE DATEADD(YEAR, n.Number * 2, Opened_Dt) <= GETDATE()
AND DATEADD(YEAR, (n.Number * 2) + 2, Opened_Dt) > GETDATE()
ORDER BY Customer_No;
Example results:
Customer_No Opened_Dt Expired
--------------------------------------
1 2010-08-02 2014-08-02
2 2013-04-27 2015-04-27
3 2013-08-24 2013-08-24
4 2015-03-19 2015-03-19
For further reading on the poor scalability of recursive CTEs see the following series: