What is the maximum number of local variables that can participate in SET operation?

Msg 8631, Level 17, State 1, Line xxx
Internal error: Server stack limit has been reached.
Please look for potentially deep nesting in your query, and try to simplify it.

This error occurs with long SET or SELECT variable assignment concatenation lists due to the way SQL Server parses and binds this type of statement - as a nested list of two-input concatenations.

For example, SET @V = @W + @X + @Y + @Z is bound into a tree of the form:

ScaOp_Arithmetic x_aopAdd
    ScaOp_Arithmetic x_aopAdd
        ScaOp_Arithmetic x_aopAdd
            ScaOp_Identifier @W 
            ScaOp_Identifier @X 
        ScaOp_Identifier @Y 
    ScaOp_Identifier @Z 

Each concatenation element after the first two results in an extra level of nesting in this representation.

The amount of stack space available to SQL Server determines the ultimate limit to this nesting. When the limit is exceeded, an exception is raised internally, which eventually results in the error message shown above. An example process call stack when the error is thrown is shown below:

Stack trace

Repro

DECLARE @SQL varchar(max);

SET @SQL = '
    DECLARE @S integer, @A integer = 1; 
    SET @S = @A'; -- Change to SELECT if you like

SET @SQL += REPLICATE(CONVERT(varchar(max), ' + @A'), 3410) +';'; -- Change the number 3410

-- SET @S = @A + @A + @A...
EXECUTE (@SQL);

This is a fundamental limit due to the way multiple concatenations are handled internally. It affects SET and SELECT variable assignment statements equally.

The workaround is to limit the number of concatenations performed in a single statement. This will also typically be more efficient, since compiling deep query trees is resource-intensive.


Inspired by @Paul's answer, I did some research and found that while it is true that stack space does limit the number of concatenations, and that stack space is a function of available memory and thus varies, the following two points are also true:

  1. there is a way to cram additional concatenations into a single statement, AND
  2. using this method to go beyond the initial stack space limitation, an actual logical limit (that does not appear to vary) can be found

First, I adapted Paul's test code to concatenate strings:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = @A';

SET @SQL += REPLICATE(CONVERT(NVARCHAR(MAX), N' + @A'), 3312) + N';';

-- SET @S = @A + @A + @A...
SET @SQL += N'SELECT DATALENGTH(@S) AS [Chars In @S];';
EXECUTE (@SQL);

With this test, the highest I could get when running on my not-so-great laptop (only 6 GB of RAM) was:

  • 3311 (returns 3312 total chars) using SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (returns 3513 total chars) using SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

before getting error 8631.

Next, I tried grouping the concatenations by using parenthesis such that the operation would be concatenating multiple groups of concatenations. For example:

SET @S = (@A + @A + @A + @A) + (@A + @A + @A + @A) + (@A + @A + @A + @A);

Doing that I was able to go well beyond the previous limits of 3312 and 3513 variables. The updated code is:

DECLARE @SQL VARCHAR(MAX), @Chunk VARCHAR(MAX);

SET @SQL = '
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = (@A+@A)';

SET @Chunk = ' + (@A' + REPLICATE(CONVERT(VARCHAR(MAX), '+@A'), 42) + ')';

SET @SQL += REPLICATE(CONVERT(VARCHAR(MAX), @Chunk), 762) + ';';

SET @SQL += 'SELECT DATALENGTH(@S) AS [Chars In @S];';

-- PRINT @SQL; -- for debug

-- SET @S = (@A+@A) + (@A + @A...) + ...
EXECUTE (@SQL);

The maximum values (for me) now are to use 42 for the first REPLICATE, thus using 43 variables per group, and then using 762 for the second REPLICATE, thus using 762 groups of 43 variables each. The initial group is hard-coded with two variables.

The output now shows that there are 32,768 characters in the @S variable. If I update the initial group to be (@A+@A+@A) instead of just (@A+@A), then I get the following error:

Msg 8632, Level 17, State 2, Line XXXXX
Internal error: An expression services limit has been reached. Please look for potentially complex expressions in your query, and try to simplify them.

Notice that the error number is different than before. It is now: 8632. AND, I have this same limit whether I use my SQL Server 2012 instance or the SQL Server 2017 instance.

It is probably no coincidence that the upper-limit here — 32,768 — is the max capacity of SMALLINT (Int16 in .NET) IF starting at 0 (the max value is 32,767 but arrays in many/most programming languages are 0-based).