我今天在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
答案 0 :(得分:25)
VanDerNorth已经链接的KB article确实包含
行聚合并置查询的正确行为是 未定义。
然后通过提供似乎表明确定性行为可能的解决方法,继续使水域变得混乱。
为了实现聚合的预期结果 连接查询,将任何Transact-SQL函数或表达式应用于 SELECT列表中的列而不是ORDER BY子句中的列。
您的有问题的查询不会将任何表达式应用于ORDER BY
子句中的列。
2005年的文章Ordering guarantees in SQL Server...确实陈述了
出于向后兼容性原因,SQL Server提供了支持 SELECT @p = @p + 1 ... ORDER BY类型的最高分配 范围。
在连接按预期工作的计划中,带有表达式[Expr1003] = Scalar Operator([@x]+[Expr1004])
的计算标量出现在排序上方。
在无法工作的计划中,计算标量显示在排序下方。正如2006年this connect item中所解释的那样,@x = @x + [msg]
表达式出现在每行的评估排序之下,但所有评估最终都使用预分配值@x
。在2006年的another similar Connect Item中,微软的回应谈到“解决”这个问题。
关于此问题的所有后续Connect项目的Microsoft响应(并且有许多)声明这不能保证
我们不保证连接的正确性 查询(比如使用带有数据检索的变量赋值) 具体顺序)。查询输出可以在SQL Server 2008中更改 取决于计划选择,表格中的数据等。您不应该 即使语法允许,依靠这种工作也能保持一致 编写一个SELECT语句,混合使用的有序行检索 变量赋值。
您所看到的行为是设计上的。使用赋值操作 (具有此示例中的串联)在具有ORDER BY子句的查询中具有 未定义的行为。这可以从发布到发布甚至更改 由于查询计划中的更改而在特定服务器版本内。 即使有解决方法,也不能依赖此行为。看到 以下知识库文章了解更多详情:
http://support.microsoft.com/kb/287515仅保证 机制如下:
- 使用游标按特定顺序循环遍历行并连接值
- 用于使用ORDER BY进行xml查询以生成连接值
- 使用CLR聚合(这不适用于ORDER BY子句)
醇>
您所看到的行为实际上是按设计进行的。这与此有关 SQL是一种集合操作语言。 SELECT中的所有表达式 列表(这也包括分配)不保证是 每个输出行只执行一次。实际上,SQL查询 优化器尽可能少地尝试执行它们。这个 当你计算的价值时会给出预期的结果 变量基于表中的一些数据,但是当你的值 分配取决于同一变量的先前值, 结果可能非常意外。如果查询优化器移动了 表达到查询树中的不同位置,它可能会得到 评估次数较少(或者只是一次,如您的一个示例中所示)。这个 这就是我们不建议使用“迭代”类型赋值的原因 计算聚合值。我们发现基于XML的解决方法......通常适用于 客户
即使没有ORDER BY,我们也不保证@var = @var + 将为任何语句生成连接值 这会影响多行。表达式的右侧可以 在查询执行期间进行一次或多次评估 我所说的行为取决于计划。
使用SELECT语句的变量赋值是专有语法 (仅限T-SQL)行为未定义或计划相关的情况 生成多行。如果需要进行字符串连接 然后使用SQLCLR聚合或基于FOR XML查询的串联或 其他关系方法。
答案 1 :(得分:2)
看起来有点像这篇文章:VARCHAR(MAX) acting weird when concatenating string
那里的结论: 这种字符串连接方法通常可以正常工作,但不能保证。 对于类似问题,The official line in the KB article是“未定义聚合并置查询的正确行为。”