A running total adds up, on each row, everything before it plus the current row: year-to-date sales, an account balance after each transaction, units sold so far… Before window functions this needed correlated subqueries that were slow and hard to read. Today SUM(...) OVER (ORDER BY ...) is enough.

Running total

The shop’s cumulative amount order by order, by date:

PostgreSQL in your browser · Ctrl+Enter to run · changes are rolled back afterwards
orderDate product totalPrice runningTotal
2022-01-21 Xbox series X 999.98 999.98
2022-06-20 MacBook Pro M1 2449.50 3449.48
2022-07-09 Xiaomi Mi 11 4304.25 7753.73
2022-07-18 Play Station 5 2199.80 9953.53
2022-09-01 Echo DOT 3 1999.50 11953.03
2022-09-01 Echo DOT 4 2999.50 14952.53
2022-09-05 Nintendo Switch 899.97 15852.50
2022-09-10 Balay Oven 1399.10 17251.60

The last row matches total sales, $17,251.60. The line ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is the window frame: it tells the engine which rows to add up at each point. Here, from the first one (UNBOUNDED PRECEDING) to the current one (CURRENT ROW).

The window frame

The frame is defined with ROWS BETWEEN start AND end, where each end can be:

Bound Meaning
UNBOUNDED PRECEDING The first row of the partition
n PRECEDING n rows before the current one
CURRENT ROW The current row
n FOLLOWING n rows after the current one
UNBOUNDED FOLLOWING The last row of the partition

ROWS vs RANGE: watch out for ties

If you write only ORDER BY without a frame, the engine uses RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW by default. The difference from ROWS shows up with ties: RANGE treats all rows with the same sort value as one, and gives them all the same running total.

PostgreSQL in your browser · Ctrl+Enter to run · changes are rolled back afterwards
orderDate product totalPrice withRows withRange
2022-07-09 Xiaomi Mi 11 4304.25 4304.25 4304.25
2022-07-18 Play Station 5 2199.80 6504.05 6504.05
2022-09-01 Echo DOT 3 1999.50 8503.55 11503.05
2022-09-01 Echo DOT 4 2999.50 11503.05 11503.05
2022-09-05 Nintendo Switch 899.97 12403.02 12403.02
2022-09-10 Balay Oven 1399.10 13802.12 13802.12

Both Echo DOTs are from September 1. With ROWS the total goes up in two steps; with RANGE both already get the sum of the two. Neither is wrong, but ROWS is almost always what’s expected, so it’s best to write the frame explicitly.

In SQL Server, ROWS is also faster than RANGE, which has to store intermediate results on disk. Another reason to always write the frame.

Running total per customer

With PARTITION BY the total resets for each group:

PostgreSQL in your browser · Ctrl+Enter to run · changes are rolled back afterwards
name orderDate product totalPrice customerRunningTotal
Helen 2022-09-01 Echo DOT 3 1999.50 1999.50
Helen 2022-09-01 Echo DOT 4 2999.50 4999.00
Janice 2022-06-20 MacBook Pro M1 2449.50 2449.50
Kimberly 2022-09-05 Nintendo Switch 899.97 899.97
Mavin 2022-01-21 Xbox series X 999.98 999.98
Mavin 2022-07-18 Play Station 5 2199.80 3199.78
Peter 2022-07-09 Xiaomi Mi 11 4304.25 4304.25

Moving average

Changing the frame to “the two previous rows and the current one” gives a three-period moving average, which smooths out spikes and is widely used to spot trends:

PostgreSQL in your browser · Ctrl+Enter to run · changes are rolled back afterwards
orderDate product totalPrice movingAverage3
2022-01-21 Xbox series X 999.98 999.98
2022-06-20 MacBook Pro M1 2449.50 1724.74
2022-07-09 Xiaomi Mi 11 4304.25 2584.58
2022-07-18 Play Station 5 2199.80 2984.52
2022-09-01 Echo DOT 3 1999.50 2834.52
2022-09-01 Echo DOT 4 2999.50 2399.60
2022-09-05 Nintendo Switch 899.97 1966.32
2022-09-10 Balay Oven 1399.10 1766.19

The first two rows don’t have three orders yet, so the average uses the ones available: one and two.

Cumulative percentage

Combining a running total with a grand total (OVER ()) gives the cumulative percentage, the basis of a Pareto analysis (which orders make up 80% of sales?):

PostgreSQL in your browser · Ctrl+Enter to run · changes are rolled back afterwards
product totalPrice cumulativePercentage
Xiaomi Mi 11 4304.25 24.9
Echo DOT 4 2999.50 42.3
MacBook Pro M1 2449.50 56.5
Play Station 5 2199.80 69.3
Echo DOT 3 1999.50 80.9
Balay Oven 1399.10 89.0
Xbox series X 999.98 94.8
Nintendo Switch 899.97 100.0

The five largest orders already account for more than 80% of sales.

Practice

  1. Compute the running total of units (productQuantity) by date.
  2. Change the moving average to use the previous row, the current one and the next one.
  3. Use LAST_VALUE with the frame ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING to show the last product ordered on every row.