[SQL SERVER][TSQL]如何計算累加值

[SQL SERVER][TSQL]如何計算累加值

今天同事詢問如何使用TSQL計算累加值,不多說直接看圖說故事比較快…XD

 

image    >>>>>   image

原始資料                                                   想要結果

 

為了達到右圖結果並維持一定的效能,我大概測試比較四種作法在效能上的差異(沒有相關索引情況下)

 

1.inner join

declare @starttime time(7),@endtime time(7),@elaspedtime numeric(20,7);
set @starttime=GETDATE(); 
;with cteA(rownumber,c1,c2)
as
(
select row_number() over(order by c2,c1 desc) as 'row',c1,c2 
from ##mytable 
)
--inner join
SELECT a.rownumber,a.c1,a.c2,sum(b.c2) as '累加量'
from cteA a
inner join cteA b 
on b.rownumber <=a.rownumber
group by a.rownumber,a.c1,a.c2
set @endtime=GETDATE(); 
set @elaspedtime = convert(integer, datediff(ms, @starttime, @endtime));
select @elaspedtime as '花費時間(ms)' 

 

 

 

 

image

花費時間(ms)= 63

 

執行計畫

image

image

整體估計成本=0.051021

 

ps:自己 join 自己 處理

 

2.Sub Query

先清除Buffers


 

dbcc dropcleanbuffers


 

 

 

 

 

 

 

 

declare @starttime time(7),@endtime time(7),@elaspedtime numeric(20,7);
set @starttime=GETDATE(); 
;with cteA(rownumber,c1,c2)
as
(
select row_number() over(order by c2,c1 desc) as 'row',c1,c2 
from ##mytable 
)
--sub query
select a.rownumber,a.c1,a.c2, a.c2 + 
isnull ( (select sum(b.c2) from cteA b where b.rownumber < a.rownumber) ,0) as '累加量'
from cteA a
order by a.rownumber
set @endtime=GETDATE(); 
set @elaspedtime = convert(integer, datediff(ms, @starttime, @endtime));
select @elaspedtime as '花費時間(ms)' 

 

 

 

 

image

花費時間(ms)= 76

 

image

image

整體估計成本=0.0522315

 

ps:利用子查詢處理不如使用 join

 

3.Loop Without Cursor(我不考慮使用Cursor)

先清除Buffers

dbcc dropcleanbuffers
 
-暫存表
declare @tmp table
(
rownumber int,
c1 varchar(10),
c2 int
)
--結果表
declare @result table
(
rownumber int,
c1 varchar(10),
c2 int,
total int default(0)
)
declare @starttime time(7),@endtime time(7),@elaspedtime numeric(20,7);
set @starttime=GETDATE() 
;with cteA(rownumber,c1,c2)
as
(
select row_number() over(order by c2,c1 desc) as 'rownumber',c1,c2 from ##mytable 
)
--將來源資料塞入暫存表
insert into @tmp
select * from cteA t1 

 
declare @maxcount int,@step int,@current int,@total int,@c1 varchar(10)
select @maxcount=count(1) from @tmp
set @step=1;set @total=0;
while(@step <= @maxcount)
begin
    select @c1=c1,@current=c2 from @tmp    
    where rownumber=@step    
    --取得累加值    
    set @total=@total+@current    
    --新增結果表    
    insert into @result    
    select @step,@c1,@current,@total                   
    set @step=@step+1
end
--結果
select t1.rownumber,t1.c1,t1.c2,t1.total as '累加量' 
from @result t1
set @endtime=GETDATE(); 
set @elaspedtime = convert(integer, datediff(ms, @starttime, @endtime));
select @elaspedtime as '花費時間(ms)' 

 

 

 

image

花費時間(ms)= 204

 

image

整體估計成本=0.0032831+..(僅列出部分)

 

ps:該方法會重複 select , insert 邏輯和實體作業,直到離開迴圈為止,

如果資料量很大可想而知對效能上的衝擊。

 

4.在 Update 中直接使用計算值

先清除Buffers

dbcc dropcleanbuffers

 

--結果表
declare @result table
(
rownumber int,
c1 varchar(10),
c2 int,
total int default(0)
)
declare @starttime time(7),@endtime time(7),@elaspedtime numeric(20,7);
set @starttime=GETDATE() 
;with cteA(rownumber,c1,c2)
as(
select row_number() over(order by c2,c1 desc) as 'rownumber',c1,c2 from ##mytable 
)
--將來源資料塞入結果表
insert into @result
select cteA.*,0 from cteA

 
declare @total int
set @total=0
update @result
--設定變數與資料行相同的值
set @total = total = @total + c2
--結果 
select t1.rownumber,t1.c1,t1.c2,t1.total as '累加量'  
from @result t1
set @endtime=GETDATE(); 
set @elaspedtime = convert(integer, datediff(ms, @starttime, @endtime));
select @elaspedtime as '花費時間(ms)' 

 

 

 

 

image

花費時間(ms)= 64

 

image

整體估計成本=0.0132843+..(僅列出部分)

 

ps:該方法類似整批處理

 

測試結果總表

image

 

可以看到 Loop Without Cursor 這方法的效能最差,其餘方法所花費時間大同小異,

個人認為 inner join在 Update 中直接使用計算值 這兩個方法效能較佳(依資料量大小選擇使用),

當然如果你有更好的方法還請告訴我,謝謝。