nvarchar concatenation/index/nvarchar(max)莫名其妙的行为

bar*_*ven 12 sql-server nvarchar string-concatenation sql-server-2012

我今天在SQL Server(2008R2和2012)中遇到了一个非常奇怪的问题.我正在尝试使用串联和select语句来构建字符串.

我找到了解决方法,但我真的很想了解这里发生了什么以及为什么它没有给我预期的结果.有人可以向我解释一下吗?

http://sqlfiddle.com/#!6/7438a/1

根据要求,这里的代码也是:

-- base table
create table bla (
    [id] int identity(1,1) primary key,
    [priority] int,
    [msg] nvarchar(max),
    [autofix] bit
)

-- table without primary key on id column
create table bla2 (
    [id] int identity(1,1),
    [priority] int,
    [msg] nvarchar(max),
    [autofix] bit
)

-- table with nvarchar(1000) instead of max
create table bla3 (
    [id] int identity(1,1) primary key,
    [priority] int,
    [msg] nvarchar(1000),
    [autofix] bit
)

-- fill the three tables with the same values
insert into bla ([priority], [msg], [autofix])
values (1, 'A', 0),
       (2, 'B', 0)

insert into bla2 ([priority], [msg], [autofix])
values (1, 'A', 0),
       (2, 'B', 0)

insert into bla3 ([priority], [msg], [autofix])
values (1, 'A', 0),
       (2, 'B', 0)
;
declare @a nvarchar(max) = ''
declare @b nvarchar(max) = ''
declare @c nvarchar(max) = ''
declare @d nvarchar(max) = ''
declare @e nvarchar(max) = ''
declare @f nvarchar(max) = ''

-- I expect this to work and generate 'AB', but it doesn't
select @a = @a + [msg]
    from bla
    where   autofix = 0
    order by [priority] asc

-- this DOES work: convert nvarchar(4000)
select @b = @b + convert(nvarchar(4000),[msg])
    from bla
    where   autofix = 0
    order by [priority] asc

-- this DOES work: without WHERE clause
select @c = @c + [msg]
    from bla
    --where autofix = 0
    order by [priority] asc

-- this DOES work: without the order by
select @d = @d + [msg]
    from bla
    where   autofix = 0
    --order by [priority] asc

-- this DOES work: from bla2, so without the primary key on id
select @e = @e + [msg]
    from bla2
    where   autofix = 0
    order by [priority] asc

-- this DOES work: from bla3, so with msg nvarchar(1000) instead of nvarchar(max)
select @f = @f + [msg]
    from bla3
    where   autofix = 0
    order by [priority] asc

select @a as a, @b as b, @c as c, @d as d, @e as e, @f as f
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 26

已经由VanDerNorth链接的知识库文章确实包含该行

未定义聚合连接查询的正确行为.

但是通过提供似乎表明确定性行为可能的解决方法,继续使水域变得混乱.

为了从聚合连接查询中获得预期结果,请将任何Transact-SQL函数或表达式应用于SELECT列表中的列,而不是ORDER BY子句中的列.

您的有问题的查询不会将任何表达式应用于ORDER BY子句中的列.

2005年的文章在SQL Server中订购保证......确实说明了

出于向后兼容性原因,SQL Server在最顶层范围内为SELECT @p = @p + 1 ... ORDER BY类型的分配提供支持.

在连接按预期工作的计划中,带有表达式的计算标量[Expr1003] = Scalar Operator([@x]+[Expr1004])出现在排序上方.

在无法工作的计划中,计算标量显示在排序下方.正如在2006年的连接项中所解释的那样,当表达式@x = @x + [msg]出现在排序下面时,它会针对每一行进行评估,但所有评估最终都使用预分配值@x.在2006 年的另一个类似的Connect Item中,微软的回应谈到了"修复"这个问题.

关于此问题的所有后续Connect项目的Microsoft响应(并且有许多)声明这不能保证

例1

我们不保证连接查询的正确性(例如使用具有特定顺序的数据检索的变量赋值).查询输出可以在SQL Server 2008中更改,具体取决于计划选择,表中的数据等.即使语法允许您编写将有序行检索与变量赋值混合的SELECT语句,也不应依赖此工作.

例2

您看到的行为是设计的.在具有ORDER BY子句的查询中使用赋值操作(此示例中的并置)具有未定义的行为.由于查询计划的更改,这可能会在不同版本之间或甚至在特定服务器版本中发生变化.即使有解决方法,也不能依赖此行为.有关更多详细信息,请参阅以下知识库文章:
http://support.microsoft.com/kb/287515 唯一保证的机制如下:

  1. 使用游标以特定顺序循环遍历行并连接值
  2. 用于使用ORDER BY进行xml查询以生成连接值
  3. 使用CLR聚合(这不适用于ORDER BY子句)

例3

您看到的行为实际上是设计的.这与SQL是一种集合操作语言有关.SELECT列表中的所有表达式(也包括赋值)都不能保证每个输出行只执行一次.事实上,SQL查询优化器尽可能少地尝试执行它们.当您根据表中的某些数据计算变量的值时,这将给出预期的结果,但是当您分配的值取决于同一变量的先前值时,结果可能非常意外.如果查询优化器将表达式移动到查询树中的其他位置,则可能会被评估的次数较少(或者只是一次,如您的一个示例中所示).这就是我们不建议使用"迭代"类型分配来计算聚合值的原因.我们发现基于XML的解决方法......通常适用于客户

例4

即使没有ORDER BY,我们也不保证@v​​ar = @var +会为影响多行的任何语句生成连接值.在查询执行期间,表达式的右侧可以被评估一次或多次,并且我所说的行为是依赖于计划的.

例5

使用SELECT语句的变量赋值是专有语法(仅限T-SQL),如果生成多行,则行为未定义或计划相关.如果需要执行字符串连接,请使用SQLCLR聚合或基于FOR XML查询的串联或其他关系方法.

  • 很棒的信息/很高兴知道。遗憾的是,Connect链接现在都死了。我尝试在新的但又可怕的UserVoice系统上找到其中的一些,但是找不到。但是,我确实找到了两个较新的链接,可以在此处链接(我对此链接的评论对它们进行了评论):https://feedback.azure.com/forums/908035-sql-server/suggestions/ 34298851-select-powered-variable-assignment-result-invalid和https://feedback.azure.com/forums/908035-sql-server/suggestions/35104033-concatenating-varchar-max-columns-into-a-string- s (2认同)