使用带有ORDER BY的SQL连接
我很困惑。您如何解释变量串联与ORDER BY之间的差异使用带有ORDER BY的SQL连接,sql,sql-server,tsql,sql-server-2014,Sql,Sql Server,Tsql,Sql Server 2014,我很困惑。您如何解释变量串联与ORDER BY之间的差异 declare @tbl table (id int); insert into @tbl values (1), (2), (3); declare @msg1 varchar(100) = '', @msg2 varchar(100) = '', @msg3 varchar(100) = '', @msg4 varchar(100) = ''; select @msg1 = @msg1 + cast(id as va
declare @tbl table (id int);
insert into @tbl values (1), (2), (3);
declare @msg1 varchar(100) = '', @msg2 varchar(100) = '',
@msg3 varchar(100) = '', @msg4 varchar(100) = '';
select @msg1 = @msg1 + cast(id as varchar) from @tbl
order by id;
select @msg2 = @msg2 + cast(id as varchar) from @tbl
order by id+id;
select @msg3 = @msg3 + cast(id as varchar) from @tbl
order by id+id desc;
select TOP(100) @msg4 = @msg4 + cast(id as varchar) from @tbl
order by id+id;
select
@msg1 as msg1,
@msg2 as msg2,
@msg3 as msg3,
@msg4 as msg4;
结果
msg1 msg2 msg3 msg4
---- ---- ---- ----
123 3 1 123
您不应该在返回多行的select中设置变量。考虑这个代码:
select top 1 @msg1 = @msg1 + cast(id as varchar) from @tbl
order by id;
select top 1 @msg2 = @msg2 + cast(id as varchar) from @tbl
order by id+id;
select top 1 @msg3 = @msg3 + cast(id as varchar) from @tbl
order by id+id desc;
select top 1 @msg4 = @msg4 + cast(id as varchar) from @tbl
order by id+id;
分别产生1、1、3和1
我很惊讶它没有引起一个例外,我很确定它曾经完全禁止这样做
基本要点仍然是一样的:SQL引擎并不像您所期望的那样,只是在程序上一个接一个地执行一些命令。它将构建一个尽可能高效的执行计划(考虑到许多限制)
另一方面,分配变量本质上是程序性的,需要明确的执行/计算顺序才能正确工作
您正在结合这两种方法-
从@tbl order by id选择id
是一种非过程查询,但是从@tbl order by id选择@id=id
是过程性的@id=id
和非常非过程性的选择的混合。SQL Server将计算结果,然后对它们进行排序,然后返回它们。在分配变量的情况下,只有第一个结果将用于填充变量。您将从排序结果集中接收第一个值,该值可以在SQL Server将扫描记录以及结果中的位置的顺序中移动
TOP将始终生成特殊的查询计划,因为它会立即强制SQL Server遵循结果的自然顺序,而不是生成查询计划,从而在统计上减少它必须读取的记录数
要解释这些差异,您必须参考SQLServer如何决定隐式排序值以优化查询
查询1
插入->表格插入->常量扫描
查询2
选择->计算标量->排序->表格扫描
查询3和查询4
选择->排序->计算标量->表格扫描
查询5和6(使用顶部)
选择->计算标量->排序(前N名)->计算标量->表格
扫描
我添加了查询6:
select top (100)
@msg5 = @msg5 + cast(id as varchar)
from @tbl
order by id+id desc
我所能看到的是,执行计划有所不同。它们都以SELECT开始,以Table Scan结束。区别在于计算标量和排序
@Msg1 has Compute Scalar then Sort. Results: 123
@Msg2 has Sort then Compute Scalar. Results: 3
@Msg3 has Sort then Compute Scalar. Results: 1
第四个是不同的,因为顶部。它仍然从选择开始,以表扫描结束,但在中间是不同的。它使用不同的排序
@Msg4 has Compute Scalar then Sort(Top N Sort) then Compute Scalar
正如许多人已经确认的那样,这不是将列中的所有行连接到一个变量中的正确方法,即使在某些情况下它确实“起作用”。如果你想看到一些替代方案,请查看 (适用于SQL Server 2008至2014和Azure SQL数据库),不应使用
SELECT
来分配局部变量。在备注中,它描述了当您使用SELECT
时,它是如何尝试行为的。需要注意的有趣点是:
- 虽然它通常只用于向变量返回单个值,但当表达式是列的名称时,它可以返回多个值
- 当表达式返回多个值时,将为变量指定最后一个返回的值
- 如果没有返回值,变量将保留其原始值(此处不直接相关,但值得注意)
SELECT@msg1=@msg1+cast(id为varchar)
本质上是SELECT@msg1+=cast(id为varchar)
,正如语法所指出的,+=
是这个表达式上可接受的复合赋值运算符。请注意,不应期望此操作在VARCHAR
上继续得到支持,也不应继续进行字符串连接-仅仅因为它在某些情况下可以工作并不意味着它可以用于生产代码
根本原因的底线是,在select表达式上运行的Compute Scalar
是使用原始id列还是id列的表达式。您可能找不到关于优化器为什么可能为每个查询选择特定计划的任何文档,但每个示例都突出显示了允许从列(因此返回并连接多行)或表达式(因此只有最后一列)计算msg值的不同用例
计算标量(变量赋值的逐行计算)发生在排序之后。这允许标量计算返回id列上的多个值,这些值通过+=
复合运算符连接在一起。我怀疑是否有具体的文档说明了原因,但似乎优化器选择在标量计算之前进行排序,因为orderby是一个列而不是一个表达式
计算标量
在排序之前完成,这使得每行中的@msg2只是(“”+id)-因此从未连接,只是id的值。同样,可能没有任何文档说明优化器为什么选择此选项,但由于order by是一个表达式,在进行排序之前,可能需要按顺序执行(id+id),作为标量计算的一部分。此时,原始列不再引用源列,但已被表达式替换。因此,如MSDN所述,您的第一列指向表达式,而不是列,因此行为将结果集的最后一个值指定给SELECT中的变量。因为你把ASC分类了,所以这里是“3”