简体   繁体   English

串联 SQL 服务器 VARCHAR(N),转换为 NVARCHAR(MAX) 时会出现意外且不一致的截断结果

[英]Concatenating SQL Server VARCHAR(N), results with unexpected and inconsistent truncated results when converted to NVARCHAR(MAX)

In the following code we see that concatenation of varchar(n) where n>4000, results with unexpected and inconsistent truncated results when converted to nvarchar(max):在下面的代码中,我们看到 varchar(n) 的串联(其中 n>4000)在转换为 nvarchar(max) 时会产生意外且不一致的截断结果:

  1. When only varchar(n) values are involved in the concatenation the result is truncated to 8000 chars.当连接中仅涉及 varchar(n) 值时,结果将被截断为 8000 个字符。
  2. A sequence of varchar(n) concatenations, which is done prior to a concatenation to an nvarchar(max), is truncated to 4000 chars, before it is concatenated to the nvarchar(max).在连接到 nvarchar(max) 之前完成的一系列 varchar(n) 连接在连接到 nvarchar(max) 之前被截断为 4000 个字符。
  3. A sequence of varchar(n) concatenations, which is done after a concatenation to an nvarchar(max), each varchar(n) is truncated to 4000 chars before it is concatenated一系列 varchar(n) 连接,在连接到 nvarchar(max) 之后完成,每个 varchar(n) 在连接之前被截断为 4000 个字符

I guess that the implicit cast is done in order of concatenation, but then I would say that (1) behave as expected, (2) should be truncated to 8000 and (3) should not be truncated at all.我想隐式转换是按连接顺序完成的,但我会说 (1) 的行为符合预期,(2) 应该被截断为 8000,而 (3) 根本不应该被截断。

It seems like there is some kind of a (wrong) length cast to 4000 that is done out of the concatenation order which is somehow related to the resulting type NVARCHAR.似乎有某种(错误的)长度转换为 4000,这是在连接顺序之外完成的,这在某种程度上与结果类型 NVARCHAR 相关。 But then if the intention was to cast all the concatenations according to the resulting type, then, since it is MAXed, no truncates would have been expected (which would give the best result).但是如果意图是根据结果类型转换所有连接,那么,因为它是 MAXed,所以不会有任何截断(这会给出最好的结果)。

Where am I wrong?我哪里错了?

DECLARE @x AS NVARCHAR(MAX)
DECLARE @y AS NVARCHAR(MAX) = '_10 chars_'
DECLARE @z AS VARCHAR(4200) = replicate('_', 4200)

SET @x = @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4200'

SET @x = @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8400 = 4200 * 2'

SET @x = @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12600 = 4200 * 3'


SET @x = @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4210 = 4200 + 10'

SET @x = @z + @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8410 = 4200 * 2 + 10'

SET @x = @z + @z + @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12610 = 4200 * 3 + 10'


SET @x = @y + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4210 = 10 + 4200'

SET @x = @y + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8410 = 10 + 4200 * 2'

SET @x = @y + @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12610 = 10 + 4200 * 3'


SET @x = @z + @z + @z + @y + @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 25210 = 4200 * 3 + 10 + 4200 * 3'

When you concatenate two varchar values together, the resulting length of the data type is the sum of the two, up to a maximum of 8000 , any longer will be truncated.当您将两个varchar值连接在一起时,数据类型的结果长度是两者的总和,最大为 8000 ,超过的长度将被截断。

When you concatenate two nvarchar values together, the resulting length of the data type is the sum of the two, up to a maximum of 4000 , any longer will be truncated.当您将两个nvarchar值连接在一起时,数据类型的结果长度是两者的总和,最大为 4000 ,超过的长度将被截断。

Only when you store it in a max variable does the value become max , the truncation will happen before then只有当您将它存储在max变量中时,该值才会变为max ,截断会在此之前发生

Furthermore, you have a second implicit conversion going on here, from varchar to nvarchar , which itself causes truncation because nvarchar can only be up to 4000.此外,您在这里进行了第二次隐式转换,从varcharnvarchar ,这本身会导致截断,因为nvarchar最多只能是 4000。

If you concatenate a nvarchar and a varchar , the varchar is converted to match, but capped at 4000. The final result will not become max until it is stored in the variable.如果您连接一个nvarchar和一个varchar ,则varchar将转换为匹配,但上限为 4000。最终结果在存储到变量中之前不会变为max So the implicit conversion will be done after that truncation.所以隐式转换将在截断之后完成。

Your results now make perfect sense:您的结果现在非常有意义:

  • The length is already 4200, and is extended to max .长度已经是 4200,并扩展到max
SET @x = @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4200'

  • In these cases, the intermediate value is capped at 8000, and only then converted to max在这些情况下,中间值上限为 8000,然后才转换为max
SET @x = @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8400 = 4200 * 2'

SET @x = @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12600 = 4200 * 3'

  • In these cases, an implicit conversion to nvarchar is done first, then concatenated with a max afterwards , so the original @z value was truncated first.在这些情况下,首先完成对nvarchar的隐式转换,然后再与max连接,因此首先截断原始@z值。
SET @x = @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4210 = 4200 + 10'

SET @x = @z + @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8410 = 4200 * 2 + 10'

SET @x = @z + @z + @z + @y
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12610 = 4200 * 3 + 10'

SET @x = @y + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 4210 = 10 + 4200'

SET @x = @y + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 8410 = 10 + 4200 * 2'

SET @x = @y + @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 12610 = 10 + 4200 * 3'

SET @x = @z + @z + @z + @y + @z + @z + @z
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 25210 = 4200 * 3 + 10 + 4200 * 3'

The only way to guarantee the right results is to cast your values to nvarchar(max) first保证正确结果的唯一方法是首先将您的值转换为nvarchar(max)

SET @x = CAST(@z AS nvarchar(max)) + CAST(@z AS nvarchar(max)) + CAST(@z AS nvarchar(max)) + @y + CAST(@z AS nvarchar(max)) + CAST(@z AS nvarchar(max)) + CAST(@z AS nvarchar(max))
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 25210 = 4200 * 3 + 10 + 4200 * 3'

Alternatively, just use CONCAT , which deals with all of this correctly anyway或者,只需使用CONCAT ,它无论如何都能正确处理所有这些

SET @x = CONCAT(@z, @z, @z, @y, @z, @z, @z);
print CAST(len(@x) AS NVARCHAR(MAX)) + '    Expected 25210 = 4200 * 3 + 10 + 4200 * 3'

db<>fiddle数据库<>小提琴

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM