12

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

2 回答 2

29

TLDR;这不是用于跨行连接字符串的记录/支持的方法。它有时有效,但有时也会失败,因为这取决于您获得的执行计划。

而是使用以下有保证的方法之一

SQL Server 2017+

SELECT @a = STRING_AGG([msg], '') WITHIN GROUP (ORDER BY [priority] ASC)
FROM bla
where   autofix = 0

SQL Server 2005+

SELECT @a = (SELECT [msg] + ''
             FROM   bla
             WHERE  autofix = 0
             ORDER  BY [priority] ASC
             FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)') 

背景

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中,微软的回应谈到了“解决”这个问题。

Microsoft 对有关此问题的所有后续 Connect 项目的响应(并且有很多)声明这根本无法保证

示例 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 查询优化器尽可能少地执行它们。当您根据表中的某些数据计算变量的值时,这将给出预期的结果,但是当您分配的值取决于同一变量的先前值时,结果可能非常出乎意料。如果查询优化器将表达式移动到查询树中的不同位置,则它可能会被评估更少的时间(或仅一次,如您的示例之一)。这就是我们不建议使用“迭代”类型分配来计算聚合值的原因。

示例 4

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

示例 5

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

于 2013-03-01T17:10:41.937 回答
2

看起来有点像这篇文章:VARCHAR(MAX) 在连接字符串时表现得很奇怪

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

于 2013-02-28T15:39:17.020 回答