sql >> Database >  >> RDS >> Sqlserver

Bereken een lopend totaal in SQL Server

Bijwerken , als u SQL Server 2012 gebruikt, zie:https://stackoverflow.com/a/10309947

Het probleem is dat de SQL Server-implementatie van de Over-clausule enigszins beperkt is.

Met Oracle (en ANSI-SQL) kunt u dingen doen als:

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

SQL Server biedt u geen schone oplossing voor dit probleem. Mijn gevoel zegt me dat dit een van die zeldzame gevallen is waarin een cursor het snelst is, hoewel ik wat benchmarking zal moeten doen voor grote resultaten.

De update-truc is handig, maar ik vind het vrij kwetsbaar. Het lijkt erop dat als u een volledige tabel bijwerkt, deze verder gaat in de volgorde van de primaire sleutel. Dus als u uw datum als primaire sleutel oplopend instelt, zult u probably wees veilig. Maar je vertrouwt op een ongedocumenteerd SQL Server-implementatiedetail (ook als de query uiteindelijk door twee procedures wordt uitgevoerd, vraag ik me af wat er zal gebeuren, zie:MAXDOP):

Volledig werkend voorbeeld:

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

Je vroeg om een ​​benchmark, dit is het dieptepunt.

De snelste VEILIGE manier om dit te doen zou de Cursor zijn, het is een orde van grootte sneller dan de gecorreleerde subquery van cross-join.

De absoluut snelste manier is de UPDATE-truc. Mijn enige zorg ermee is dat ik er niet zeker van ben dat de update onder alle omstandigheden lineair zal verlopen. Er is niets in de query dat dit expliciet zegt.

Kortom, voor productiecode zou ik met de cursor gaan.

Testgegevens:

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. Query Performance Insight:ontdekken wat resources van uw Azure SQL-database verbruikt?

  2. Wanneer MongoDB of andere documentgeoriënteerde databasesystemen gebruiken?

  3. Advies met behulp van draaitabel in Oracle

  4. Rijen verwijderen uit bovenliggende en onderliggende tabellen