使用 SQL 连接 ORDER BY
Using SQL concatenation with ORDER BY
我很困惑。您如何解释使用 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 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 中设置超过一行的 returns 变量。考虑这段代码:
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 引擎不只是按程序执行一些命令,如您所料,一个接一个。它将构建一个尽可能高效的执行计划(给定 许多 约束)。
另一方面,分配变量本质上是程序性的,需要明确的执行/评估顺序才能正常工作。
您正在组合这两种方法 - select id from @tbl order by id
是一个非过程查询,但是 select @id = id from @tbl order by id
是过程 @id = id
和非常多的非过程查询的混合select.
SQL服务器会计算结果,然后排序,然后return。在分配变量的情况下,只有第一个结果将用于填充您的变量。您正在从排序的结果集中接收第一个值,它可以按顺序移动 SQL 服务器将扫描记录以及结果中的位置。
TOP 将始终生成特殊的查询计划,因为它会立即强制 SQL 服务器坚持结果的自然排序,而不是生成统计上会减少必须读取的记录数的查询计划。
要解释差异,您必须参考 SQL 服务器如何决定对值进行隐式排序以优化查询。
查询 1
Insert -> Table Insert -> Constant Scan
查询 2
SELECT -> Compute Scalar -> Sort -> Table Scan
查询 3 和 4
SELECT -> Sort -> Compute Scalar -> Table Scan
查询 5 和 6(使用 TOP)
SELECT -> Compute Scalar -> Sort (Top N) -> Compute Scalar -> Table
Scan
我添加了查询 6:
select top (100)
@msg5 = @msg5 + cast(id as varchar)
from @tbl
order by id+id desc
我只能看到执行计划有所不同。它们都以 SELECT 开头并以 Table 扫描结束。区别在于计算标量和排序之间。
@Msg1 has Compute Scalar then Sort. Results: 123
@Msg2 has Sort then Compute Scalar. Results: 3
@Msg3 has Sort then Compute Scalar. Results: 1
第四个因顶不同。还是以select开始,以table扫描结束,只是中间不一样了。它使用不同的排序。
@Msg4 has Compute Scalar then Sort(Top N Sort) then Compute Scalar
正如许多人证实的那样,这不是将一列中的所有行连接到一个变量中的正确方法 - 尽管在某些情况下确实如此 "work"。如果您想查看一些替代方案,请查看 this blog。
According to MSDN(适用于 SQL 服务器 2008 到 2014 和 Azure SQL 数据库),SELECT
不应用于分配局部变量。在备注中,它描述了当您使用 SELECT
时它会如何尝试表现。有趣的注意事项:
- 虽然通常它应该只用于 return 一个变量的单个值,但当表达式是列的名称时,它可以 return 多个值。
- 当表达式 return 多个值时,变量被赋值为 returned 的最后一个值。
- 如果没有值被 returned,变量保留其原始值(此处不直接相关,但值得注意)。
这里的前两点是关键 - 连接恰好起作用,因为 SELECT @msg1 = @msg1 + cast(id as varchar)
本质上是 SELECT @msg1 += cast(id as varchar)
,并且正如语法注释,+=
是一个可接受的复合赋值运算符表达。请注意,不应期望此操作会继续在 VARCHAR
上得到支持并进行字符串连接 - 仅仅因为它在某些情况下可以正常工作并不意味着它可以用于生产代码。
关于根本原因的底线是在 select 表达式上运行的 Compute Scalar
是使用原始 id 列还是 id 列的表达式。您可能找不到任何关于为什么优化器可能会为每个查询选择特定计划的文档,但是每个示例都突出显示了不同的用例,这些用例允许从列中评估 msg 值(因此多行是 return ed 和连接)或表达式(因此只有最后一列)。
@msg1 是“123”,因为 Compute Scalar
(变量赋值的逐行评估)发生在 Sort
之后。这允许标量计算 return id 列上的多个值,通过 +=
复合运算符连接它们。我怀疑是否有具体的文档说明原因,但似乎优化器选择在标量计算之前进行排序,因为排序依据是列而不是表达式。
@msg2 是 '3' 因为 Compute Scalar
在排序之前完成,这使得每行中的 @msg2 只是 ('' + id) - 所以永远不会连接, 只是 id 的值。同样,可能没有任何优化器选择它的文档,但似乎由于 order by 是一个表达式,所以它可能需要在 order by 中执行 (id+id) 作为标量计算的一部分,然后才能排序。此时,您的原始列不再引用源列,而是已被表达式替换。因此,如 MSDN 所述,您的第一列指向一个表达式,而不是列,因此该行为将结果集的最后一个值分配给 SELECT 中的变量。由于您对 ASC 进行了排序,因此此处得到“3”。
@msg3 为 '1' 的原因与示例 2 相同,只是您订购的是 DESC。同样,这成为评估中的表达式 - 而不是原始列,因此赋值得到 DESC 顺序的最后一个值,所以你得到'1'。
@msg4 再次为“123”,因为 TOP
操作强制对 ORDER BY
进行初始标量评估,以便它可以确定您的前 100 条记录。这与示例 2 和 3 不同,在示例 2 和 3 中,标量计算同时包含 order by 和 select 计算,这导致每个示例都是一个表达式,而不是引用回原始列。示例 4 的 TOP 将 ORDER BY 和 SELECT 计算分开,因此在应用 SORT (TOP N SORT) 之后,它会对 SELECT 列进行标量计算,此时您仍然引用原始列(不是列的表达式),因此它 return 多行允许发生连接。
来源:
我很困惑。您如何解释使用 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 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 中设置超过一行的 returns 变量。考虑这段代码:
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 引擎不只是按程序执行一些命令,如您所料,一个接一个。它将构建一个尽可能高效的执行计划(给定 许多 约束)。
另一方面,分配变量本质上是程序性的,需要明确的执行/评估顺序才能正常工作。
您正在组合这两种方法 - select id from @tbl order by id
是一个非过程查询,但是 select @id = id from @tbl order by id
是过程 @id = id
和非常多的非过程查询的混合select.
SQL服务器会计算结果,然后排序,然后return。在分配变量的情况下,只有第一个结果将用于填充您的变量。您正在从排序的结果集中接收第一个值,它可以按顺序移动 SQL 服务器将扫描记录以及结果中的位置。
TOP 将始终生成特殊的查询计划,因为它会立即强制 SQL 服务器坚持结果的自然排序,而不是生成统计上会减少必须读取的记录数的查询计划。
要解释差异,您必须参考 SQL 服务器如何决定对值进行隐式排序以优化查询。
查询 1
Insert -> Table Insert -> Constant Scan
查询 2
SELECT -> Compute Scalar -> Sort -> Table Scan
查询 3 和 4
SELECT -> Sort -> Compute Scalar -> Table Scan
查询 5 和 6(使用 TOP)
SELECT -> Compute Scalar -> Sort (Top N) -> Compute Scalar -> Table Scan
我添加了查询 6:
select top (100)
@msg5 = @msg5 + cast(id as varchar)
from @tbl
order by id+id desc
我只能看到执行计划有所不同。它们都以 SELECT 开头并以 Table 扫描结束。区别在于计算标量和排序之间。
@Msg1 has Compute Scalar then Sort. Results: 123
@Msg2 has Sort then Compute Scalar. Results: 3
@Msg3 has Sort then Compute Scalar. Results: 1
第四个因顶不同。还是以select开始,以table扫描结束,只是中间不一样了。它使用不同的排序。
@Msg4 has Compute Scalar then Sort(Top N Sort) then Compute Scalar
正如许多人证实的那样,这不是将一列中的所有行连接到一个变量中的正确方法 - 尽管在某些情况下确实如此 "work"。如果您想查看一些替代方案,请查看 this blog。
According to MSDN(适用于 SQL 服务器 2008 到 2014 和 Azure SQL 数据库),SELECT
不应用于分配局部变量。在备注中,它描述了当您使用 SELECT
时它会如何尝试表现。有趣的注意事项:
- 虽然通常它应该只用于 return 一个变量的单个值,但当表达式是列的名称时,它可以 return 多个值。
- 当表达式 return 多个值时,变量被赋值为 returned 的最后一个值。
- 如果没有值被 returned,变量保留其原始值(此处不直接相关,但值得注意)。
这里的前两点是关键 - 连接恰好起作用,因为 SELECT @msg1 = @msg1 + cast(id as varchar)
本质上是 SELECT @msg1 += cast(id as varchar)
,并且正如语法注释,+=
是一个可接受的复合赋值运算符表达。请注意,不应期望此操作会继续在 VARCHAR
上得到支持并进行字符串连接 - 仅仅因为它在某些情况下可以正常工作并不意味着它可以用于生产代码。
关于根本原因的底线是在 select 表达式上运行的 Compute Scalar
是使用原始 id 列还是 id 列的表达式。您可能找不到任何关于为什么优化器可能会为每个查询选择特定计划的文档,但是每个示例都突出显示了不同的用例,这些用例允许从列中评估 msg 值(因此多行是 return ed 和连接)或表达式(因此只有最后一列)。
@msg1 是“123”,因为
Compute Scalar
(变量赋值的逐行评估)发生在Sort
之后。这允许标量计算 return id 列上的多个值,通过+=
复合运算符连接它们。我怀疑是否有具体的文档说明原因,但似乎优化器选择在标量计算之前进行排序,因为排序依据是列而不是表达式。@msg2 是 '3' 因为
Compute Scalar
在排序之前完成,这使得每行中的 @msg2 只是 ('' + id) - 所以永远不会连接, 只是 id 的值。同样,可能没有任何优化器选择它的文档,但似乎由于 order by 是一个表达式,所以它可能需要在 order by 中执行 (id+id) 作为标量计算的一部分,然后才能排序。此时,您的原始列不再引用源列,而是已被表达式替换。因此,如 MSDN 所述,您的第一列指向一个表达式,而不是列,因此该行为将结果集的最后一个值分配给 SELECT 中的变量。由于您对 ASC 进行了排序,因此此处得到“3”。@msg3 为 '1' 的原因与示例 2 相同,只是您订购的是 DESC。同样,这成为评估中的表达式 - 而不是原始列,因此赋值得到 DESC 顺序的最后一个值,所以你得到'1'。
@msg4 再次为“123”,因为
TOP
操作强制对ORDER BY
进行初始标量评估,以便它可以确定您的前 100 条记录。这与示例 2 和 3 不同,在示例 2 和 3 中,标量计算同时包含 order by 和 select 计算,这导致每个示例都是一个表达式,而不是引用回原始列。示例 4 的 TOP 将 ORDER BY 和 SELECT 计算分开,因此在应用 SORT (TOP N SORT) 之后,它会对 SELECT 列进行标量计算,此时您仍然引用原始列(不是列的表达式),因此它 return 多行允许发生连接。
来源: