SQLQuokka
Start practicing
QuestionsLearnSandboxFeaturesRoadmapFAQ
SQL Concept

SQL Running Totals and Moving Averages with Window Functions

A running total adds each row to everything before it; a moving average smooths values over the last few rows. Both are window functions: an aggregate like SUM or AVG with an OVER clause that says which rows to include.

Examples run in MySQL 8 and PostgreSQL · Updated

In short

  • SUM(x) OVER (ORDER BY d) is a running total.
  • Add PARTITION BY to restart the total for each group.
  • ROWS BETWEEN 2 PRECEDING AND CURRENT ROW limits the window to three rows.
  • Unlike GROUP BY, every row stays in the result.

How the window grows

With ORDER BY inside OVER, the window for each row runs from the first row up to the current one, so SUM becomes a running total. Without ORDER BY, every row would see the whole partition and get the grand total.

A frame clause fixes the window size. ROWS BETWEEN 2 PRECEDING AND CURRENT ROW means the current row and the two before it, a 3-row moving window. The first rows simply have fewer rows to average.

Ties in the sort column

The default frame is RANGE, which treats rows with the same ORDER BY value as one step, so tied rows show the same running total. Add a tie-breaker or use ROWS when each row should step on its own.

The example tables

Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.

orders

order_idcustomer_idorder_dateamount
10112025-01-05120
10212025-01-2080
10322025-01-07200
10412025-02-02150
10532025-02-1090
10622025-02-2560
10732025-03-01130
10812025-03-0340

Examples

Running total of revenue

Each row adds its amount to the total so far.

SELECT order_id, order_date, amount,
       SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders
ORDER BY order_date;
Result
order_idorder_dateamountrunning_total
1012025-01-05120120
1032025-01-07200320
1022025-01-2080400
1042025-02-02150550
1052025-02-1090640
1062025-02-2560700
1072025-03-01130830
1082025-03-0340870

Running total per customer

PARTITION BY restarts the total for each customer.

SELECT customer_id, order_date, amount,
       SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS customer_total
FROM orders
ORDER BY customer_id, order_date;
Result
customer_idorder_dateamountcustomer_total
12025-01-05120120
12025-01-2080200
12025-02-02150350
12025-03-0340390
22025-01-07200200
22025-02-2560260
32025-02-109090
32025-03-01130220

3-order moving average

The average of the current order and the two before it.

SELECT order_date, amount,
       ROUND(AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 1) AS moving_avg_3
FROM orders
ORDER BY order_date;
Result
order_dateamountmoving_avg_3
2025-01-05120120.0
2025-01-07200160.0
2025-01-2080133.3
2025-02-02150143.3
2025-02-1090106.7
2025-02-2560100.0
2025-03-0113093.3
2025-03-034076.7

Common mistakes

  • Missing ORDER BY in OVER. SUM(amount) OVER () gives the grand total on every row, not a running total.
  • Ties with the default frame. Rows sharing a date get the same running total under RANGE. Add a unique tie-breaker such as an id.
  • A self join instead. Summing earlier rows with a self join works but is slow and easy to get wrong. Window functions are the expected answer.

Practise Running Totals & Moving Averages

Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.

  1. 3-Day Moving Average of Orders

    Smooth daily order counts with a trailing three-day average.

    Medium✓ Solved
  2. Accounts That Went Negative

    Use running balances to find accounts that ever overdrew and when it first happened.

    Medium✓ Solved
  3. Products Driving 80% of Revenue

    Find the smallest set of top products whose cumulative revenue reaches 80% of the total.

    Medium✓ Solved
  4. Last Passenger to Board

    Find the last person who can board a bus with a 1000 kg weight limit.

    Medium✓ Solved
  5. User Session Reconstruction

    Group raw click events into sessions using a 30-minute inactivity rule.

    Hard✓ Solved
  6. Three-Month Salary Window

    Each employee's rolling three-month salary total, excluding their most recent month.

    Hard✓ Solved

Questions people ask

What is the difference between ROWS and RANGE?
ROWS counts physical rows; RANGE groups rows with equal ORDER BY values. For a fixed-size moving window, use ROWS.
Can I compute a 7-day rolling sum?
If there is exactly one row per day, ROWS BETWEEN 6 PRECEDING AND CURRENT ROW works. With gaps between days, fill in missing dates first, or join on a date range.