今天,我在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


评论

那是一个很好的例子,但是您可以包括一些在问题中重现该问题所需的代码吗? SQLFiddle非常有用,但是代码不应仅存在于此。

你到底是什么意思这是SQL中的问题,而不是其他问题。对吗?

我的意思是您在SQLfiddle上拥有的repro,但是在问题的代码块中。

啊。当然。将其添加到问题。 :)

#1 楼

VanDerNorth已链接的知识库文章确实包含以下行:


聚合级联查询的正确行为是
未定义。


但随后通过提供似乎确实表明确定性行为的变通方法来使工作变得有些混乱。


为了从总体上获得预期结果
串联查询,对SELECT列表中的列而不是ORDER BY子句中的列应用任何Transact-SQL函数或表达式。您遇到问题的查询不应用任何表达式请参阅ORDER BY子句中的列。

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


由于向后兼容的原因,SQL Server提供了对
SELECT @p = @p + 1 ...类型的赋值在最上方的范围
。如您所料,计算标量在排序上方出现带有表达式[Expr1003] = Scalar Operator([@x]+[Expr1004])的表达式。

在无法正常工作的计划中,计算标量出现在排序下方。如2006年的该连接项中所述,当表达式@x = @x + [msg]出现在该排序的下方时,将为每一行对其进行评估,但所有评估最终都使用@x的预分配值。在2006年发布的另一个类似的Connect Item中,Microsoft的答复是“解决”该问题。

Microsoft在此问题上所有以后的Connect Item的响应(并且有很多)指出,这很简单不能保证

示例1


我们不对串联查询的正确性做任何保证
(例如在数据库中使用变量赋值和数据检索)
具体顺序)。在SQL Server 2008中,查询输出可以更改,具体取决于计划选择,表中的数据等。即使语法允许您
,也不应
始终依靠此方法工作。编写SELECT语句,将有序行检索与变量分配混合在一起。


示例2


您看到的行为是设计。在带有ORDER BY子句的查询中使用赋值操作
(在此示例中为串联)具有未定义的行为。由于查询计划的变化,在特定服务器版本中,发行版之间可能会发生变化,甚至可能

即使存在变通办法,您也不能依赖此行为。有关更多详细信息,请参见下面的知识库文章:http://support.microsoft.com/kb/287515唯一保证的
机制如下:


使用光标以特定顺序在行中循环并连接值
通过ORDER BY用于xml查询以生成连接的值
使用CLR聚合(这不适用于ORDER BY子句)



示例3


您看到的行为实际上是设计使然。这与
SQL是一种集操作语言有关。不能保证SELECT
列表中的所有表达式(也包括赋值)对于每个输出行仅精确地执行一次。实际上,SQL查询优化器会尽力减少执行次数。当您基于表中的某些数据计算
变量的值时,此
将提供预期的结果,但是当您
分配的值取决于相同表的先前值时变量,结果可能出乎意料。如果查询优化器将
表达式移动到查询树中的其他位置,则它得到的
求值次数可能更少(如您的示例之一)。这就是为什么我们不建议使用“迭代”类型分配来计算聚合值的原因。我们发现基于XML的解决方法...通常对于
客户来说很有效


示例4


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


示例5


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


评论


很好的信息/很高兴知道。遗憾的是,Connect链接现在都死了。我尝试在新的但又可怕的UserVoice系统上找到其中的一些,但是找不到。但是,我确实找到了两个较新的链接,可以在此处链接(我用指向此答案的链接对它们进行了评论):feedback.azure.com/forums/908035-sql-server/suggestions/…和反馈。 azure.com/forums/908035-sql-server/suggestions / ...

–所罗门·鲁兹基
19年5月24日在18:59

因此,Microsoft一方面表示我们需要使用CLR,另一方面,Azure不支持CLR-brentozar.com/archive/2016/04/…。

–罗马·佩卡(Roman Pekar)
19-10-9在9:29

@RomanPekar-在Azure(或任何最新版本)上,只需使用STRING_AGG

–马丁·史密斯
19-10-9在9:41

@MartinSmith是的,它确实适用于字符串连接,但不适用于更复杂的情况-stackoverflow.com/questions/58288057/…。使用此@var = @var + 1有点类似于匿名用户定义的聚合。好吧,不完全是,不可能使用group by子句,但是,它仍然提供了一些不错的可能性。

–罗马·佩卡(Roman Pekar)
19-10-9在9:43

#2 楼

似乎有点像这样的帖子:VARCHAR(MAX)在连接字符串时表现得很奇怪

那里的结论:
这种字符串连接方法通常可以用,但不能保证。
知识库文章中针对类似问题的官方说法是:“未定义聚合级联查询的正确行为。”

评论


嗯谢谢。不过,“未定义的行为”并不能真正满足我的需求。紧接着,您引用的知识库文章适用于SQL Server 2000和7.0。现在不应该解决吗?

– \ bartlaarhoven
13年2月28日在15:43

@bartlaarhoven-无法修复任何问题,因为从未保证过该行为,因此您不应该依赖它。有关替代方法,请参见在Transact-SQL中并置行值。

–马丁·史密斯
13年1月1日在11:29