nvarchar(max) still being truncated
因此,我正在MS SQL Server 2008中编写存储过程。这是一个非常长的查询,必须动态编写,因此我创建了一个名为
1 2 3 4 5 6 7 8 | DECLARE @Query NVARCHAR(MAX); SET @Query = 'SELECT...' -- some of the query gets set here SET @Query = @Query + '...' -- more query gets added on, etc. -- later on... PRINT LEN(@Query) -- Prints out 4273, which is correct as far as I can tell PRINT @Query -- Truncates value to 4000 characters EXEC sp_executesql @Query -- totally crashes due to malformed (truncated) query |
我是在做错误的事情,还是在
问题似乎与SET语句有关。我认为表达式的大小不能超过4,000个字节。如果您要尝试做的就是分配动态生成的超过4,000个字符的语句,则无需对任何设置进行任何更改。您需要做的是拆分作业。如果您的语句长度为6,000个字符,请找到一个逻辑断点,然后将下半部分连接到同一变量。例如:
1 2 3 | SET @Query = 'SELECT ....' [Up TO 4,000 characters, THEN rest OF statement AS below] SET @Query = @Query + [rest OF statement] |
现在按常规方式运行查询,即
问题在于隐式转换。
如果您要连接的是Unicode / nChar / nVarChar值,则SQL Server会将您的字符串隐式转换为nVarChar(4000),很遗憾,它太笨拙以至于无法意识到它会截断您的字符串,甚至会警告您数据已被删除。删减了这个问题!
连接长字符串(或您可能觉得很长的字符串)时,请始终使用CAST(''作为nVarChar(MAX))预先连接字符串构建,如下所示:
1 2 3 | SET @Query = CAST('' AS nVarChar(MAX))--Force implicit conversion to nVarChar(MAX) + 'SELECT...'-- some of the query gets set here + '...'-- more query gets added on, etc. |
认为这就是SQL Server的工作原理,这是多么痛苦和令人恐惧。 :(
我知道网络上的其他解决方法是使用多个变量将您的代码分成多个SET / SELECT分配,但是鉴于上述解决方案,这是不必要的。
对于那些字符数最多达到8000个字符的用户,可能是因为您没有Unicode,所以将其隐式转换为VarChar(8000)。
说明:
幕后发生的事情是,即使您要分配的变量使用(MAX),SQL Server也会评估您首先分配的值的右侧,默认情况下分配给nVarChar(4000)或VarChar(8000)(取决于关于您要连接的内容)。完成计算后(为您截断了值),然后在将其分配给变量时将其转换为(MAX),但那时为时已晚。
要查看生成的动态SQL,请更改为文本模式(快捷方式: sub> Ctrl-T),然后使用SELECT
1 2 3 | PRINT LEN(@Query) -- Prints out 4273, which is correct as far as I can tell --SET NOCOUNT ON SELECT @Query |
至于
1 2 3 4 5 6 | DECLARE @n nvarchar(MAX) SET @n = REPLICATE(CONVERT(nvarchar(MAX), 'a'), 4500) SET @N = 'SELECT ''' + @n + '''' print @n -- up to 4000 SELECT @n -- up to max EXEC sp_Executesql @n |
结果为文本最多只能包含8192个字符。
我用这种方法
1 2 3 4 5 6 7 8 9 10 11 | DECLARE @Query NVARCHAR(MAX); SET @Query = REPLICATE('A',4000) SET @Query = @Query + REPLICATE('B',4000) SET @Query = @Query + REPLICATE('C',4000) SET @Query = @Query + REPLICATE('D',4000) SELECT LEN(@Query) SELECT @Query /*Won't contain any"D"s*/ SELECT @Query AS [processing-instruction(x)] FOR XML PATH /*Not truncated*/ |
您的第一个问题是
查询格式错误的原因可能不是截断。
Print将varchar(MAX)截断为8000,nvarchar(MAX)截断为4000字符。
但;
1 | PRINT CAST(@query AS NTEXT) |
将打印整个查询。
使用字符串表达式创建动态SQL的问题在于,SQL确实将字符串表达式的求值限制为4,000个字符。您可以为nvarchar(max)变量分配一个更长的字符串,但是只要在表达式中包含+(例如+ CASE ... END +),表达式结果就被限制为4,000个字符。
解决此问题的一种方法是使用CONCAT而不是+。例如:
1 2 3 4 5 | SET @SQL = CONCAT(@SQL, N' ... dynamic SQL statements ... ', CASE ... END, N' ... dynamic SQL statements ... ') |
其中@sql声明为nvarchar(max)。
使用此
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | IF OBJECT_ID('tempdb..#printBig') IS NOT NULL DROP PROCEDURE #printBig GO CREATE PROCEDURE #printBig ( @text NVARCHAR(MAX) ) AS --DECLARE @text NVARCHAR(MAX) = 'YourTextHere' DECLARE @lineSep NVARCHAR(2) = CHAR(13) + CHAR(10) -- Windows DECLARE @off INT = 1 DECLARE @maxLen INT = 4000 DECLARE @len INT WHILE @off < LEN(@text) BEGIN SELECT @len = CASE WHEN LEN(@text) - @off - 1 <= @maxLen THEN LEN(@text) ELSE @maxLen - CHARINDEX(REVERSE(@lineSep), REVERSE(SUBSTRING(@text, @off, @maxLen))) - LEN(@lineSep) + 1 END PRINT SUBSTRING(@text, @off, @len) --PRINT '@off=' + CAST(@off AS VARCHAR) + ' @len=' + CAST(@len AS VARCHAR) SET @off += @len + LEN(@lineSep) END |
资源:
https://www.richardswinbank.net/doku.php?id=tsql:print_big
我今天遇到了同样的问题,发现超出4000个字符的限制后,我不得不将动态查询分为两个字符串,并在执行查询时将它们串联起来。
1 2 3 4 5 6 | DECLARE @Query NVARCHAR(MAX); DECLARE @Query2 NVARCHAR(MAX); SET @Query = 'SELECT...' -- some of the query gets set here SET @Query2 = '...' -- more query gets added on, etc. EXEC (@Query + @Query2) |