How to get a running total with MSSQL
There are basically 4 ways of producing running totals in Microsoft SQL Server:
- The front end, the report writer
- Cross joins
- Sub-query
- Cursors
- I will NOT provide a cursor example, based on religious grounds.
- I do not believe in cursors. SQL is a set language, not a record based language.
- In this case cursors are lot more efficient on small data sets, but grind to a halt on large data sets.
- Microsoft SQL Server does NOT support the correct way of producing running totals.
- The proper way of producing running totals is to use the windowing of the sum.
- Oracle and DB2 do support the windowing on the sum.
select abc, amount, sum(amount) over (order by amount, abc) as 'Running total' from xyz order by 2;
Data used
go
SET STATISTICS TIME ON
SET STATISTICS IO ON
SELECT customerid,
salesorderid,
subtotal AS 'Order amount'
FROM sales.salesorderheader
WHERE customerid <= 3
ORDER BY customerid, salesorderid;
go
customerid salesorderid Order amount ----------- ------------ --------------------- 1 43860 13216.0537 1 44501 23646.0339 1 45283 34066.1881 1 46042 31423.5209 2 46976 9216.3596 2 47997 4949.8589 2 49054 1574.1247 2 50216 1751.5987 2 51728 3534.1671 2 57044 4106.6501 2 63198 3668.7336 2 69488 822.009 3 44124 18555.8149 3 44791 17419.6078 3 45568 18285.6382 3 46377 11796.1804 3 47439 71112.1316 3 48378 51198.5404 3 49538 27899.9579 3 50748 47138.0027 3 53616 83436.1754 3 59011 40109.0555 3 65310 15431.5196 3 71889 31559.7518 (24 row(s) affected) Table 'SalesOrderHeader'. Scan count 1, logical reads 74, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 16 ms, elapsed time = 1 ms. SQL Server parse and compile time: CPU time = 10 ms, elapsed time = 10 ms.
Cross joins
- A cross join returns the cartesian product of two joined tables.
- A cartesian product is a set of all the possible combinations.
- There are 2 ways of doing a cross join, and both produce the same result:
- Implicit: select ... from abc, xyz;
- Explicit: select ... from abc cross join xyz
Implicit cross join
a.salesorderid,
a.subtotal AS 'Order amount',
SUM(b.subtotal) AS 'Running total'
FROM sales.salesorderheader a,
sales.salesorderheader b
WHERE (a.customerid >= b.customerid) and
(a.salesorderid >= b.salesorderid) and
(a.customerid <= 3)
GROUP BY a.customerid, a.salesorderid, a.subtotal
ORDER BY a.customerid
go
customerid salesorderid Order amount Running total ----------- ------------ --------------------- --------------------- 1 43860 13216.0537 13216.0537 1 44501 23646.0339 36862.0876 1 45283 34066.1881 70928.2757 1 46042 31423.5209 102351.7966 2 46976 9216.3596 111568.1562 2 47997 4949.8589 116518.0151 2 49054 1574.1247 118092.1398 2 50216 1751.5987 119843.7385 2 51728 3534.1671 123377.9056 2 57044 4106.6501 127484.5557 2 63198 3668.7336 131153.2893 2 69488 822.009 131975.2983 3 71889 31559.7518 565917.6745 3 65310 15431.5196 533535.9137 3 59011 40109.0555 514435.6605 3 53616 83436.1754 470219.9549 3 50748 47138.0027 383249.6124 3 49538 27899.9579 334360.011 3 48378 51198.5404 304885.9284 3 47439 71112.1316 248737.5291 3 46377 11796.1804 168409.0379 3 45568 18285.6382 125189.3366 3 44791 17419.6078 72837.5103 3 44124 18555.8149 31771.8686 (24 row(s) affected) Table 'SalesOrderHeader'. Scan count 25, logical reads 847, physical reads 29, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 8 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 10 ms.
Explicit cross join
a.salesorderid,
a.subtotal AS 'Order amount',
SUM(b.subtotal) AS RunningTotal
FROM sales.salesorderheader a CROSS JOIN sales.salesorderheader b
WHERE (b.customerid <= a.customerid) and
(b.salesorderid <= a.salesorderid) and
(a.customerid <= 3)
GROUP BY a.customerid,a.salesorderid,a.subtotal
ORDER BY a.customerid,a.salesorderid;
go
customerid salesorderid Order amount RunningTotal ----------- ------------ --------------------- --------------------- 1 43860 13216.0537 13216.0537 1 44501 23646.0339 36862.0876 1 45283 34066.1881 70928.2757 1 46042 31423.5209 102351.7966 2 46976 9216.3596 111568.1562 2 47997 4949.8589 116518.0151 2 49054 1574.1247 118092.1398 2 50216 1751.5987 119843.7385 2 51728 3534.1671 123377.9056 2 57044 4106.6501 127484.5557 2 63198 3668.7336 131153.2893 2 69488 822.009 131975.2983 3 44124 18555.8149 31771.8686 3 44791 17419.6078 72837.5103 3 45568 18285.6382 125189.3366 3 46377 11796.1804 168409.0379 3 47439 71112.1316 248737.5291 3 48378 51198.5404 304885.9284 3 49538 27899.9579 334360.011 3 50748 47138.0027 383249.6124 3 53616 83436.1754 470219.9549 3 59011 40109.0555 514435.6605 3 65310 15431.5196 533535.9137 3 71889 31559.7518 565917.6745 (24 row(s) affected) Table 'SalesOrderHeader'. Scan count 25, logical reads 847, physical reads 29, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 3 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 7 ms.
Look at the execution times:
- The implicit cross join: CPU time = 0 ms, elapsed time = 8 ms.
- The explicit cross join: CPU time = 0 ms, elapsed time = 3 ms.
What a difference! Even the compile time for the explicit cross join is faster.
Subtotal subquery
a.salesorderid,
a.subtotal,
(SELECT SUM(b.subtotal)
FROM sales.salesorderheader b
WHERE b.customerid <= a.customerid and
b.salesorderid <= a.salesorderid) AS 'Running total'
FROM sales.salesorderheader a
WHERE a.customerid <= 3
ORDER BY a.customerid, a.salesorderid
customerid salesorderid subtotal Running total ----------- ------------ --------------------- --------------------- 1 43860 13216.0537 13216.0537 1 44501 23646.0339 36862.0876 1 45283 34066.1881 70928.2757 1 46042 31423.5209 102351.7966 2 46976 9216.3596 111568.1562 2 47997 4949.8589 116518.0151 2 49054 1574.1247 118092.1398 2 50216 1751.5987 119843.7385 2 51728 3534.1671 123377.9056 2 57044 4106.6501 127484.5557 2 63198 3668.7336 131153.2893 2 69488 822.009 131975.2983 3 44124 18555.8149 31771.8686 3 44791 17419.6078 72837.5103 3 45568 18285.6382 125189.3366 3 46377 11796.1804 168409.0379 3 47439 71112.1316 248737.5291 3 48378 51198.5404 304885.9284 3 49538 27899.9579 334360.011 3 50748 47138.0027 383249.6124 3 53616 83436.1754 470219.9549 3 59011 40109.0555 514435.6605 3 65310 15431.5196 533535.9137 3 71889 31559.7518 565917.6745 (24 row(s) affected) Table 'SalesOrderHeader'. Scan count 25, logical reads 16946, physical reads 6, read-ahead reads 699, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 94 ms, elapsed time = 113 ms.
Conclusion
The fastest is:
- The front end, the report writer. It does NOT any CPU cycle of the SQL server.
- Cross-joins
- Sub-query
I expected the sub-query to be slower, but not that slow!
- Cursors are another beast. On small data sets, cursors will often be faster than a sub-query.
- The larger the data set, the faster the cross-join will be against the cursors, with the threshold toward the middle size of the data set.
- Cursors do NOT scale. Lets see the performance of cursors on 10 million rows.

