sql >> Databasteknik >  >> RDS >> Sqlserver

Beräkna en löpande summa i SQL Server

Uppdatera , om du kör SQL Server 2012 se:https://stackoverflow.com/a/10309947

Problemet är att SQL Server-implementeringen av Over-klausulen är något begränsad.

Oracle (och ANSI-SQL) låter dig göra saker som:

 SELECT somedate, somevalue,
  SUM(somevalue) OVER(ORDER BY somedate 
     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
          AS RunningTotal
  FROM Table

SQL Server ger dig ingen ren lösning på detta problem. Min magkänsla säger mig att detta är ett av de sällsynta fallen där en markör är den snabbaste, även om jag måste göra några benchmarking på stora resultat.

Uppdateringstricket är praktiskt men jag känner att det är ganska bräckligt. Det verkar som att om du uppdaterar en fullständig tabell kommer den att fortsätta i ordningen för primärnyckeln. Så om du ställer in ditt datum som en primärnyckel stigande kommer du probably var försiktig. Men du förlitar dig på en odokumenterad SQL Server-implementeringsdetalj (även om frågan slutar utföras av två processer undrar jag vad som kommer att hända, se:MAXDOP):

Fullständigt fungerande prov:

drop table #t 
create table #t ( ord int primary key, total int, running_total int)

insert #t(ord,total)  values (2,20)
-- notice the malicious re-ordering 
insert #t(ord,total) values (1,10)
insert #t(ord,total)  values (3,10)
insert #t(ord,total)  values (4,1)

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t
order by ord 

ord         total       running_total
----------- ----------- -------------
1           10          10
2           20          30
3           10          40
4           1           41

Du bad om ett riktmärke, detta är lowdown.

Det snabbaste SÄKRA sättet att göra detta på skulle vara markören, den är en storleksordning snabbare än den korrelerade underfrågan för cross-join.

Det absolut snabbaste sättet är UPDATE-tricket. Min enda bekymmer med det är att jag inte är säker på att uppdateringen under alla omständigheter kommer att fortsätta på ett linjärt sätt. Det finns inget i frågan som uttryckligen säger det.

Sammanfattningsvis, för produktionskod skulle jag gå med markören.

Testdata:

create table #t ( ord int primary key, total int, running_total int)

set nocount on 
declare @i int
set @i = 0 
begin tran
while @i < 10000
begin
   insert #t (ord, total) values (@i,  rand() * 100) 
    set @i = @i +1
end
commit

Test 1:

SELECT ord,total, 
    (SELECT SUM(total) 
        FROM #t b 
        WHERE b.ord <= a.ord) AS b 
FROM #t a

-- CPU 11731, Reads 154934, Duration 11135 

Test 2:

SELECT a.ord, a.total, SUM(b.total) AS RunningTotal 
FROM #t a CROSS JOIN #t b 
WHERE (b.ord <= a.ord) 
GROUP BY a.ord,a.total 
ORDER BY a.ord

-- CPU 16053, Reads 154935, Duration 4647

Test 3:

DECLARE @TotalTable table(ord int primary key, total int, running_total int)

DECLARE forward_cursor CURSOR FAST_FORWARD 
FOR 
SELECT ord, total
FROM #t 
ORDER BY ord


OPEN forward_cursor 

DECLARE @running_total int, 
    @ord int, 
    @total int
SET @running_total = 0

FETCH NEXT FROM forward_cursor INTO @ord, @total 
WHILE (@@FETCH_STATUS = 0)
BEGIN
     SET @running_total = @running_total + @total
     INSERT @TotalTable VALUES(@ord, @total, @running_total)
     FETCH NEXT FROM forward_cursor INTO @ord, @total 
END

CLOSE forward_cursor
DEALLOCATE forward_cursor

SELECT * FROM @TotalTable

-- CPU 359, Reads 30392, Duration 496

Test 4:

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t

-- CPU 0, Reads 58, Duration 139


  1. Hur LEFT()-funktionen fungerar i MySQL

  2. Grunderna i sys.dm_exec_requests

  3. Lägga till en inledande nolla till vissa värden i kolumn i MySQL

  4. Vad är det bästa sättet att skapa och fylla i en taltabell?