TLDR; – no. The limit is your memory capabilities.
In below example the script runs a cte in loop. It iterates over a selected number of ctes; each cte refers to the previous one. As a result we retrieve 1 initial row defined at the beginning. Optionally we can measure the SQL performance recording the time when the row has been added (to a table variable).
On a regular home laptop, the query produces a memory error after 2000 iterations.
Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) Sep 24 2019 13:48:23 Copyright (C) 2019 Microsoft Corporation Express Edition (64-bit) on Windows 10 Pro 10.0 <X64> (Build 19045: )
SET NOCOUNT ON DECLARE @CounterLimit int = 1000 --declare number of iterations DECLARE @sql nvarchar(max) DECLARE @finalsql nvarchar(max) DECLARE @statstable table --declare statistics of measures (id int, timestamp datetime) DECLARE @presql nvarchar(max)= --header of sql query, initializing cte1 'WITH cte1 AS (SELECT 1 AS id ,''val'' AS val) ' DECLARE @Counter INT SET @Counter=1 WHILE ( @Counter <= @CounterLimit) --while loop BEGIN SET @sql = CONCAT( COALESCE(@sql,cast('' as nvarchar(max))) ,CAST(' ,cte' AS NVARCHAR(MAX)) ,cast(@Counter+1 as nvarchar(max)) ,cast(' AS (SELECT * from cte' as NVARCHAR(MAX)) --iterating the names of cte ,cast(@Counter as nvarchar(max)) ,cast(') ' as nvarchar(max)) ) insert into @statstable --while loop statistics values (@Counter,GETUTCDATE()) SET @Counter = @Counter + 1 END DECLARE @postsql nvarchar(max)= --closing a query cast('SELECT * FROM cte' as nvarchar(max)) +cast(@CounterLimit as nvarchar(max)) set @finalsql = concat(@presql, @sql, @postsql) -- select * from @statstable --optional statistics exec sys.sp_executesql @finalsql