子查询或leftjoin与哪个更快的组?

rah*_*rma 7 sql-server-2005 subquery running-total left-join

查询执行计划我必须在我的应用程序中显示总计列的运行总数...所以我使用以下查询来查找运行总计..​​.我发现两者都按照我的需要工作.在一个我使用左连接与group by和另一个我使用子查询.

现在我的问题是,当我的数据每天增加数千时,哪一个更快,如果数据将限制在1000或2000行,那么哪一个更好......而且任何其他方法比这两个更快? ??

declare @tmp table(ind int identity(1,1),col1 int)
insert into @tmp
select 2
union
select 4
union
select 7
union 

select 5
union
select 8
union 
select 10



SELECT t1.col1,sum( t2.col1)
FROM @tmp AS t1 LEFT JOIN @tmp t2 ON t1.ind>=t2.ind
group by t1.ind,t1.col1


select t1.col1,(select sum(col1) from  @tmp as t2 where t2.ind<=t1.ind)
from @tmp as t1
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 5

计算SQL Server中运行总计的一个很好的资源是Itzik Ben Gan的这份文档,该文档作为其竞选活动的一部分提交给SQL Server团队,以使该OVER子句从其最初的SQL Server 2005实现进一步扩展.在其中,他展示了一旦​​你进入成千上万行游标执行基于集合的解决方案.SQL Server 2012确实扩展了该OVER子句,使得这种查询变得更加容易.

SELECT col1,
       SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
FROM   @tmp 
Run Code Online (Sandbox Code Playgroud)

正如您在SQL Server 2005上一样,但是您无法使用它.

Adam Machanic 在这里展示了如何使用CLR来改进标准TSQL游标的性能.

对于此表定义

CREATE TABLE RunningTotals
(
ind int identity(1,1) primary key,
col1 int
)
Run Code Online (Sandbox Code Playgroud)

我在数据库中创建了包含2,000行和10,000行的表,并且在ALLOW_SNAPSHOT_ISOLATION ON此设置中创建了一个(原因是我的初始结果是在数据库中设置了导致结果令人费解的方面).

所有表的聚簇索引只有1个根页.每个叶页的数量如下所示.

+-------------------------------+-----------+------------+
|                               | 2,000 row | 10,000 row |
+-------------------------------+-----------+------------+
| ALLOW_SNAPSHOT_ISOLATION OFF  |         5 |         22 |
| ALLOW_SNAPSHOT_ISOLATION ON   |         8 |         39 |
+-------------------------------+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

我测试了以下案例(链接显示执行计划)

  1. 左加入和分组
  2. 相关子查询 2000行计划,10000行计划
  3. 来自Mikael(更新)答案的CTE
  4. CTE如下

包含额外CTE选项的原因是为了提供CTE解决方案,如果ind不保证色谱柱顺序排列,该解决方案仍然有效.

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
DECLARE @col1 int, @sumcol1 bigint;

WITH    RecursiveCTE
AS      (
        SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
        FROM RunningTotals
        ORDER BY ind
        UNION   ALL
        SELECT  R.ind, R.col1, R.Total
        FROM    (
                SELECT  T.*,
                        T.col1 + Total AS Total,
                        rn = ROW_NUMBER() OVER (ORDER BY T.ind)
                FROM    RunningTotals T
                JOIN    RecursiveCTE R
                        ON  R.ind < T.ind
                ) R
        WHERE   R.rn = 1
        )
SELECT  @col1 =col1, @sumcol1=Total
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0);
Run Code Online (Sandbox Code Playgroud)

添加了所有查询CAST(col1 AS BIGINT)以避免运行时出现溢出错误.另外,对于所有这些,我将结果分配给上面的变量,以便消除从考虑中发回结果所花费的时间.

结果

+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  |          |        |          Base Table        |         Work Table         |     Time        |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  | Snapshot | Rows   | Scan count | logical reads | Scan count | logical reads | cpu   | elapsed |
| Group By         | On       | 2,000  | 2001       | 12709         |            |               | 1469  | 1250    |
|                  | On       | 10,000 | 10001      | 216678        |            |               | 30906 | 30963   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 1140  | 1160    |
|                  | Off      | 10,000 | 10001      | 130089        |            |               | 29906 | 28306   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| Sub Query        | On       | 2,000  | 2001       | 12709         |            |               | 844   | 823     |
|                  | On       | 10,000 | 2          | 82            | 10000      | 165025        | 24672 | 24535   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 766   | 999     |
|                  | Off      | 10,000 | 2          | 48            | 10000      | 165025        | 25188 | 23880   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE No Gaps      | On       | 2,000  | 0          | 4002          | 2          | 12001         | 78    | 101     |
|                  | On       | 10,000 | 0          | 20002         | 2          | 60001         | 344   | 342     |
|                  | Off      | 2,000  | 0          | 4002          | 2          | 12001         | 62    | 253     |
|                  | Off      | 10,000 | 0          | 20002         | 2          | 60001         | 281   | 326     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE Alllows Gaps | On       | 2,000  | 2001       | 4009          | 2          | 12001         | 47    | 75      |
|                  | On       | 10,000 | 10001      | 20040         | 2          | 60001         | 312   | 413     |
|                  | Off      | 2,000  | 2001       | 4006          | 2          | 12001         | 94    | 90      |
|                  | Off      | 10,000 | 10001      | 20023         | 2          | 60001         | 313   | 349     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
Run Code Online (Sandbox Code Playgroud)

相关子查询和GROUP BY版本都使用"三角形"嵌套循环连接,由RunningTotals表(T1)上的聚簇索引扫描驱动,并且对于该扫描返回的每一行,寻求返回到表(T2)自连接T2.ind<=T1.ind.

这意味着重复处理相同的行.当T1.ind=1000行被处理的自连接检索和资金的所有行有ind <= 1000,则在下一行T1.ind=1001相同的1000个检索行再次和一个附加行等一起总结.

2,000行表的此类操作的总数为2,001,000,一般为10k行50,005,000或更多(n² + n) / 2 ,其明显呈指数增长.

在2000行的情况下的主要区别GROUP BY和子查询的版本是,前者具有流聚合后加入等具有三列供给到它(T1.ind,T2.col1,T2.col1)和一个GROUP BY的属性T1.ind,而后者被称为一个标量集合计算,在连接之前使用流聚合,只T2.col1进入它并且根本没有GROUP BY设置属性.可以看出,这种更简单的布置在减少CPU时间方面具有可测量的益处.

对于10,000行的情况,子查询计划中存在另外的差异.它添加了一个急切的假脱机,将所有ind,cast(col1 as bigint)值复制到其中tempdb.在快照隔离开启的情况下,这比聚集索引结构更紧凑,并且净效果是将读取次数减少大约25%(因为基表为版本控制信息保留了相当多的空白空间),当此选项关闭时,它会变得不那么紧凑(可能是因为bigintvs int差异)并且读取结果更多.这减少了子查询和组之间的差距,但子查询仍然获胜.

然而,明显的赢家是递归CTE.对于"无间隙"版本,来自基表的逻辑读取现在2 x (n + 1)反映n索引搜索到2级索引以检索所有行加上最后一个不返回任何行并终止递归的行.这仍然意味着20,002次读取处理22页表!

递归CTE版本的逻辑工作表读取非常高.似乎每个源行有6个工作表读取.这些来自存储前一行随后的输出指数阀芯再次下一次迭代中(这由Umachandar贾雅彻德伦很好的解释看这里).尽管数量很高,但这仍然是表现最好的.