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

时间:2017-01-04 10:40:29

标签: 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

Here's the query on the data explorer

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

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

  • 使@t.number@sum具有相同的类型(将@sum设为int@t.numberbigint)。
  • 删除外部order by
  • 删除内部order by
  • 通过将order by添加到内部版本或从外部版本中删除desc来使from @t排序方向相同
  • 删除子查询(即只选择int

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

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

类似的事情发生在字符串而不是数字上,因此不限于bigintMicrosoft 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: )

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

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: )

a.png

(后者是数据浏览器)。

发生了什么事?

2 个答案:

答案 0 :(得分:1)

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

根据文件:

  

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

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

这对于&#34; group concat&#34;的许多功能最为明显。诀窍停止工作:

SELECT @sentence = @sentence + ' ' + word from  SENTENCE_WORDS order by position

这些通常被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)')

答案 1 :(得分:0)

删除第二个ORDER BY(即#34;按编号desc&#34排序)。

您正在使用T-SQL的未记录功能(我相信它被称为ROW连接?),但不保证在未来的SQL版本中可以使用。它有点hacky,但非常有用!正如您所发现的,当您使用ORDER BY子句时它会中断。这是使用行连接的已知问题。