问题
Using SQL Server 2012, I've found that trying to build up a string based on an nvarchar(max) column in a table doesn't seem to work correctly. It seems to overwrite, instead of append. Arbitrary Example:
DECLARE @sql nvarchar(max);
SELECT @sql = N'';
SELECT @sql += [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT @sql;
This SHOULD print out all the SQL module definitions for all the various dt_ tables in the database, separated by GO, as a script that could then be run. However... this prints out only the LAST module definition, not the sum of all of them. It's behaving as if the "+=" were just an "=".
If you change it just slightly... cast [definition] to an nvarchar(4000) for example, it suddenly works as expected. Also, if you choose any other column that is NOT an nvarchar(max) or varchar(max) type, it works as expected. Example:
DECLARE @sql nvarchar(max);
SELECT @sql = N'';
SELECT @sql += CAST([definition] AS nvarchar(4000)) + '
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT @sql;
Is this a known bug? Or is this working as expected? Am I doing something wrong? Is there any way for me to make this work correctly? I've tried a dozen different things, including ensuring every expression in the concatenation is the same nvarchar(max) type, including the string literal.
NOTE: The example is just an example that shows the problem, and not exactly what I'm trying to do in real life. If your database doesn't have the "dt*" tables defined, you can change the WHERE clause to specify any group of tables or stored procedures in any database you want, you'll get the same result... only the last one shows up in the @sql string, as if you just did "=" instead of "+=". Also, explicitly stating "@sql = @sql + " behaves the same way... works correctly with every string type EXCEPT nvarchar(max) or varchar(max).
I've verified that none of the [definition] values is NULL as well, so there are no NULL shenanigans going on.
回答1:
The += operator only applies to numeric data types in SQL Server. Microsoft documentation here
For string concatenation, you need to write the assignment and concatenation separately.
DECLARE @sql nvarchar(max);
SELECT @sql = N'';
SELECT @sql = @sql + [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT @sql;
Also, if you are running this query in Management Studio, keep in mind that there is a limit to the size of the data that it will return (including in a print statement). So if the definitions of your modules exceed this limit, they will be truncated in the output.
回答2:
It works as expected:
DECLARE @sql nvarchar(max);
SELECT @sql =COALESCE(@sql + N' GO ','')+[definition]
FROM sys.sql_modules
Print @sql;
回答3:
The following code demonstrates taking a column value from a set of ordered rows and, using for xml, creating an aggregated string with separators, including line breaks, between values.
-- Carriage return and linefeed characters.
declare @CRLF as NVarChar(16) = NChar( 13 ) + NChar( 10 );
-- Carriage return and linefeed characters XML encoded.
declare @EncodedCRLF as NVarChar(16) = N' ';
-- Separator to insert between source rows in the accumulated string.
declare @Separator as NVarChar(64) = ';' + @EncodedCRLF + 'go;' + @EncodedCRLF + 'do something with ';
-- Characters in @Separator to remove from the first element in @Result .
declare @SkipCount as Int = 4 + 2 * Len( @EncodedCRLF );
-- The result.
declare @Result as NVarChar( max ) = '';
-- Do it.
select @Result =
Replace(
Stuff(
-- Specify the sorting order of the XML elements in the following select statement.
( select @Separator + name from sys.tables order by name desc for XML path(''), type).value('.[1]', 'VarChar(max)' ),
1, @SkipCount, '' ),
@EncodedCRLF, @CRLF );
-- Output the result.
select @Result as [Result];
-- Output the result with line breaks shown.
print @Result; -- Switch to Messages tab in SSMS to see result.
回答4:
To add on to @HABO's comment, the behavior of aggregate string concatenation is undefined and results are plan dependent. Use FOR XML to provide deterministic results and honor the ORDER BY clause:
DECLARE @sql nvarchar(max);
SET @sql =
(SELECT [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id)
FOR XML PATH(''), TYPE).value('(./text())[1]', 'nvarchar(MAX)');
PRINT @sql; --SSMS will truncate long strings
回答5:
I´m pretty sure the PRINT command is the reason for the troubles.
When I try:
DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @sql += SUBSTRING( [definition], 1, 100 ) + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
FROM SYS.SQL_MODULES
PRINT @sql;
I get all my procedures (the first 100 chars ...). So it seems to be a limitation to the PRINT command.
Second try, another database, without "substring":
DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @sql += [definition] + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
FROM SYS.SQL_MODULES
PRINT @sql;
Now I got 1,5 procedures: The 1st one complete, the second stopped in the middle of on line in the proc. There is a limit in PRINT
Third try, to proof my assumption:
DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @sql += [definition] + 'GO' FROM SYS.SQL_MODULES
PRINT @sql;
Now I got one line of code more! So for me, this is the proof, that PRINT has somehow a limit.
A last attempt:
DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @sql += [definition] + N'GO' FROM SYS.SQL_MODULES
PRINT SUBSTRING(@SQL,1,4000)
PRINT SUBSTRING(@SQL,4001,4000)
The result: Now I got more then 2 procedures, although there now is a line break after 4K, this may occur on ... bad places.
来源:https://stackoverflow.com/questions/51315886/concatenating-nvarcharmax-values-doesnt-seem-to-work-working-as