关于sql:nvarchar(max)仍被截断

nvarchar(max) still being truncated

因此,我正在MS SQL Server 2008中编写存储过程。这是一个非常长的查询,必须动态编写,因此我创建了一个名为@Query的变量并将其设置为NVARCHAR(MAX)类型。 现在,有人告诉我,在现代版本的SQL Server中,NVARCHAR(MAX)可以容纳大量的数据,远远超过了原来的最大4000个字符。 但是,当我尝试打印出@Query时,它仍然被截断为4000个字符。

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

我是在做错误的事情,还是在NVARCHAR(MAX)的工作原理上完全错误?


问题似乎与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]

现在按常规方式运行查询,即EXEC ( @Query )


问题在于隐式转换。

如果您要连接的是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,请更改为文本模式(快捷方式: 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

至于sp_executesql,请尝试此操作(在文本模式下),它应该显示三个aaaaa...,中间的那个是最长的,再加上'SELECT ..'。观察右下角状态栏中的Ln... Col..指示器,在第二个输出的末尾显示4510。

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个字符。

Screenshot

我用这种方法

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语句的限制。我不确定为什么sp_executesql失败。它应该支持几乎任何长度的输入。

查询格式错误的原因可能不是截断。


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)。


使用此PRINT BIG函数输出所有内容:

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)