为select中的每一行重新分配变量的令人惊讶的行为

bal*_*pha 5 sql-server

这是我可以重现问题的最简单的例子.因此它看起来有点做作,但请耐心等待.

declare @t table(number int)
insert into @t values (1), (2)

declare @sum bigint = 0

select @sum = @sum + number
    from (select top 2 number from @t order by number) subquery
    order by number desc

select @sum
Run Code Online (Sandbox Code Playgroud)

这是关于数据资源管理器的查询.

我希望这会返回3,即表中值的总和@t.相反,它返回1.

执行以下任何操作都会导致查询正确返回3:

  • 使@t.number@sum具有相同类型的(通过使@sum一个int@t.number一个bigint).
  • 去掉外面的 order by
  • 去掉内心 order by
  • order by通过添加desc到内部或从外部移除它来使两者在同一方向上排序
  • 删除子查询(即只选择from @t)

这些事情都不会让我觉得应该改变这个查询的行为.

交换排序顺序(在子查询中降序,在外部升序)将使查询返回2而不是1.

类似的事情发生在字符串而不是数字上,所以这并不局限于intbigint.

SQL Server 2014和2016都会发生这种情况,或者说准确

Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 
Feb 20 2014 20:04:26 
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows NT 6.3 <X64> (Build 10586: )
Run Code Online (Sandbox Code Playgroud)

Microsoft SQL Server 2016 (RTM-CU1) (KB3164674) - 13.0.2149.0 (X64)
Jul 11 2016 22:05:22
Copyright (c) Microsoft Corporation
Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600: )
Run Code Online (Sandbox Code Playgroud)

(后者是数据浏览器).

这是怎么回事?

Ben*_*Ben 1

答案似乎是您正在/正在依赖 Sql Server 2012 中更改的未记录行为。

根据文档:

SELECT @local_variable 通常用于将单个值返回到变量中。但是,当表达式是列名时,它可以返回多个值。如果 SELECT 语句返回多个值,则为变量分配最后返回的值。

如果(要分配给的)目标变量是源表达式的一部分,则没有记录会发生什么情况。看来这种行为已经改变了。在早期版本中,将为每行分配一次变量,但这种情况似乎不再发生。

这对于“组连接”技巧不再起作用的许多函数来说最为明显:

SELECT @sentence = @sentence + ' ' + word from  SENTENCE_WORDS order by position
Run Code Online (Sandbox Code Playgroud)

这些通常必须被 xml concat 技巧取代。

set @sentence = (
    select word as "text()", ' ' as "text()" 
    from SENTENCE_WORDS 
    order by position 
    for xml path(''), root('root'), type
).value('(/root)[1]', 'nvarchar(max)')
Run Code Online (Sandbox Code Playgroud)