nvarchar(max) 仍然被截断

Posted

技术标签:

【中文标题】nvarchar(max) 仍然被截断【英文标题】:nvarchar(max) still being truncated 【发布时间】:2011-06-17 12:58:28 【问题描述】:

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

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) 的工作方式完全错误?

【问题讨论】:

NVARCHAR(MAX) 从不有 4000 个字符的限制..... 也遇到过在 SQL Server 2019 中使用 NVARCHAR(MAX) 将字符串参数截断为 4270 个字符的问题 【参考方案1】:

问题在于隐式转换。

如果您要连接 Unicode/nChar/nVarChar 值,则 SQL Server 会将您的字符串隐式转换为 nVarChar(4000),不幸的是,它太愚蠢了,无法意识到它会截断您的字符串,甚至给您一个警告:数据已被截断! 当连接长字符串(或您认为可能很长的字符串)时,总是使用 CAST('' as nVarChar(MAX)) 预先连接您的字符串构建,如下所示:

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)。 警告: 您仍然不能拥有大于 4000(或 VarChar 为 8000)的单个完整文字字符串。 文字字符串是您硬编码并用撇号包装的字符串。 您必须分解这些字符串,否则 SQL Server 将在连接之前截断每个字符串。 我每 20 行(左右)添加' + ',以确保我不会超过。 这是平均每行最多 200 个字符 - 但请记住,空格仍然很重要! 说明: 幕后发生的事情是,即使您分配给的变量使用 (MAX),SQL Server 也会首先评估您分配的值的右侧,默认为 nVarChar(4000) 或 VarChar(8000)(取决于关于你要连接的内容)。完成连接并计算出值后(并在为您截断后),然后在将其分配给您的变量时将其转换为 (MAX),但到那时为时已晚。

【讨论】:

这个答案是关于确切的问题。 杀手级功能:S ...我改为使用 NVARCHAR(4000)。即使使用此解决方案,也无法在不截断的情况下使 NVARCHAR(MAX) 工作。在我的情况下,截断为 256 个字符(简单的 SELECT CONCAT() 使用带有 NVARCHAR(MAX) 的函数,并且仅在 SQLCMD 中 - 注意这不是显示宽度问题。 有关隐式转换如何成为此处问题的更详细说明,请参阅关于同一主题的另一个 SO 问题中的 this answer。 与 CAST('' as nVarChar(MAX)) 的预连接解决了我的问题,非常感谢!我试图做一个 UPDATE dbo.MyTable SET MyColumn (of type nvarchar(max)) = 'string long than 4000' 并且在我应用这个解决方法之前它一直截断字符串 (len(MyColumn) = 4000 after running the update) .执行 UPDATE dbo.MyTable SET MyColumn = CAST('' as nVarChar(MAX)) + 'string long than 4000' 产生了预期的结果。 @Adam 我最近遇到了同样的问题并找到了解决方法。因为是加了一堆Literal Strings,以前没注意,今天有一个大于4000,所以被Silently Truncated了。我用“警告”更新了答案,并包含了解决方法(分解你的大文字字符串)。【参考方案2】:

问题似乎与 SET 语句有关。我认为表达式的大小不能超过 4,000 字节。如果您要做的只是分配超过 4,000 个字符的动态生成语句,则无需对任何设置进行任何更改。你需要做的是拆分你的任务。如果您的语句有 6,000 个字符长,请找到一个逻辑断点,然后将后半部分连接到同一个变量。例如:

SET @Query = 'SELECT ....' [Up To 4,000 characters, then rest of statement as below]

SET @Query = @Query + [rest of statement]

现在正常运行您的查询,即EXEC ( @Query )

【讨论】:

这确实解决了我长期以来遇到的一个问题。非常感谢。 这应该是答案,看来发帖人在他的任何一个“SET”语句中都连接了超过 4000 个字符,NVARCHAR(MAX) 确实存储了超过 4000 个字符 同意这应该是答案。 嗯,那是三年前的事了,所以我不能确定哪个应该是答案。我选择的那一个可能对我帮助最大,但这个也对其他人有帮助,这很好。 顺便说一句,如果您使用 DECLARE 创建查询也是如此,例如DECLARE @my_query NVARCHAR(MAX) = '... some really long query ...' 也将截断 4000 个字符。在 SQL Server 2008 上遇到了这个问题。【参考方案3】:

要查看生成的动态 SQL,请切换到文本模式(快捷键: Ctrl-T),然后使用 SELECT

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。

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

【讨论】:

MS SQL Server 中有一个默认设置,限制文本结果中每列的最大字符数。要更改它,请转到查询菜单 > 查询选项 > 结果 > 文本并将最大值更改为 8192 之类的值。 一旦我发现PRINT 对它们进行了格式化,我就停止使用SELECT 来打印我的动态查询......但很高兴知道SELECT 不会截断它们。跨度> @Cyrena - 尽管我的显然设置为 256,但它实际上并没有限制显示。哦,好吧。 @Cyr - SSMS 中的默认值(至少在 2008 年)是 20 亿左右,iirc。并且 (n)(var)char 是免疫的,它指的是旧的“文本”类型,也是 iirc @cyberkiwi 你能在 SSMS 中打印这个吗? CAST(REPLICATE(N'A',4000) AS NVARCHAR(max)) + REPLICATE(N'B',4000) + REPLICATE(N'C',4000) + REPLICATE(N'D',4000)【参考方案4】:

结果到文本只允许最多 8192 个字符。

我使用这种方法

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*/

【讨论】:

@cyberkiwi - 使用 PI 停止 SQL Server 替换 XML 实体(即 << 等)【参考方案5】:

Print 将 varchar(MAX) 截断为 8000,nvarchar(MAX) 截断为 4000 个字符。

但是;

PRINT CAST(@query AS NTEXT)

将打印整个查询。

【讨论】:

【参考方案6】:

您的第一个问题是PRINT 语句的限制。我不确定为什么sp_executesql 失败了。它应该支持几乎任何长度的输入。

也许查询格式错误的原因不是截断。

【讨论】:

那不是EXEC语句的限制吗? 不,EXEC 至少可以达到 2GB 不确定,但 Exec (sp_executesql) 在 sql2005 中确实有 8000 个字符的限制【参考方案7】:

使用字符串表达式创建动态 SQL 的问题在于 SQL 确实将字符串表达式的求值限制为 4,000 个字符。您可以将更长的字符串分配给 nvarchar(max) 变量,但只要在表达式中包含 +(例如 + CASE ... END + ),表达式结果就会被限制为 4,000 个字符。

解决此问题的一种方法是使用 CONCAT 而不是 +。例如:

SET @sql = CONCAT(@sql, N'
     ... dynamic SQL statements ...
    ', CASE ... END, N'
     ... dynamic SQL statements ...
    ')

@sql 被声明为 nvarchar(max)。

【讨论】:

【参考方案8】:

我今天也遇到了同样的问题,发现超过 4000 个字符的限制,我不得不将动态查询分成两个字符串,并在执行查询时将它们连接起来。

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)

【讨论】:

EXEC sp_executesql (@Query + @Query2) 失败。它需要一个 nvarchar 类型的语句。 谢谢。 EXEC本身可以将字符串连接成单个nvarchar,我不知道为什么我首先要放入sp_executesql! 我发现SET @Query = @Query + '...' 也同样有效【参考方案9】:

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

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 \r\n

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

【讨论】:

【参考方案10】:

我正在创建 JSON-LD 来创建站点审查脚本。 **DECLARE @json VARCHAR(MAX);**实际的 JSON 大约是 94K。

我通过使用 CAST('' AS VARCHAR(MAX)) + @json 来实现这一点,正如其他贡献者所解释的那样:- 所以**SET @json = CAST('' AS VARCHAR(MAX)) + (SELECT .....**

2/ 我还必须更改查询选项:- 查询选项 -> 'results' -> 'grid' -> 'Maximum Characters received' -> 'non-XML Data' 设置为 2000000。 (我将 'results' -> 'text' -> '每列显示的最大字符数' 保留为默认值)

【讨论】:

以上是关于nvarchar(max) 仍然被截断的主要内容,如果未能解决你的问题,请参考以下文章

NVARCHAR(MAX) 不能容纳 50478 个字符

SSIS字符串截断错误

插入时截断的长字符串

T-SQL字符串相加之后被截断的那点事

NVARCHAR 和 VARCHAR 转换导致索引扫描

insertselectvarchar长度限制