Tier 3 · Intermediate

Aggregate Window Functions (SUM, AVG, COUNT OVER) in SQL

By Owen Middleton · Updated September 2026 · Examples run on PostgreSQL 17

What are Aggregate Window Functions in SQL?

SUM, AVG, and COUNT used with OVER give you two different behaviors depending on whether you include ORDER BY inside OVER.

Without ORDER BY inside OVER, the aggregate computes a single value for the partition and places that same value on every row. With ORDER BY inside OVER, the aggregate accumulates: each row gets the sum (or average, or count) of all rows up to and including its position in the ordering. These are two different analytical questions, and the presence or absence of ORDER BY is what separates them.

How do you compute a running total in SQL?

You're tracking order revenue over time. You want each order to show both the overall total and the running total as of that date:

SELECT
  ordered_at::date AS order_date,
  total_amount,
  SUM(total_amount) OVER () AS grand_total,
  SUM(total_amount) OVER (ORDER BY ordered_at) AS running_total
FROM orders
ORDER BY ordered_at

grand_total is the same on every row — the sum of all orders. running_total starts small and grows with each row. On the last row, it equals grand_total.

How do you restart a running total per group?

The same logic applies to other aggregates. Add PARTITION BY to reset within groups:

SUM(total_amount)   OVER (PARTITION BY status ORDER BY ordered_at)  -- running total per status
AVG(total_amount)   OVER (PARTITION BY status ORDER BY ordered_at)  -- running average per status
COUNT(*)            OVER (PARTITION BY status ORDER BY ordered_at)  -- running count per status

Each accumulates within its partition, restarting at the start of each new group.

COUNT(*) counts rows regardless of NULL. COUNT(column) counts only non-NULL values. SUM, AVG, MIN, and MAX all skip NULL values, consistent with how they behave as regular aggregates.

Why does my running total jump ahead on tied timestamps?

The one thing that trips people up: when multiple rows share the same ORDER BY value, the running total jumps ahead.

PostgreSQL's default behavior for ordered windows groups rows with the same sort value together and includes all of them in each other's running total. If three orders all have the same ordered_at timestamp, each of those three rows shows a running total that already includes all three of their amounts — not a strict row-by-row accumulation.

What is the difference between ROWS and RANGE in a window frame?

This is called RANGE mode. If you want strict row-by-row accumulation regardless of equal sort values, add ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:

SUM(total_amount) OVER (
  ORDER BY ordered_at
  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)

In ROWS mode, each row accumulates only up to its own physical position. For time-series work where multiple events share a timestamp, this usually produces the result you expect.

Check your understanding

You write SUM(revenue) OVER (ORDER BY order_date). Two rows share the same order_date. What does each row's running total include?

Practice Aggregate Window Functions in SQL

Practice · easy ecommerce · Brightlane

Brightlane's finance team computes the cumulative revenue total as orders are processed in sequence by ID.

Write a query to return the ID and amount of every order, plus the running total of total_amount accumulated from the first order through that order in order of id.

Assumptions:

  • The orders table has one row per order with an id and a total_amount.
  • Orders are processed in ascending id order. The running total at each row is the combined total_amount across every order whose id is less than or equal to that row's id.

Output:

  • One row per order, with columns id, total_amount, and running_total.
Schema · ecommerce5 tables? = nullable
categories
idinteger
nametext
parent_id?integer
products
idinteger
nametext
category_id?integer
pricenumeric
stock_qtyinteger
attributes?jsonb
order_items
idinteger
order_id?integer
product_id?integer
quantityinteger
unit_pricenumeric
customers
idinteger
nametext
emailtext
city?text
countrytext
created_attimestamptz
is_activeboolean
orders
idinteger
customer_id?integer
ordered_attimestamptz
statustext
total_amountnumeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution

The full breakdown walks through the shape, each clause, why this approach beats the alternatives, and the trap to avoid.

See the full worked solution
In the game

Station Zero, our free browser SQL game, teaches this concept inside a story. No signup.

run a running total in Station Zero

9 Aggregate Window Functions practice problems

Write a query to return the ID and amount of every order, plus the running total of total_amount accumulated from the first order through that order in order of id.

easy ecommerce

Write a query to return the ID and status of every order, plus the running count of orders from the first record through that order in order of id.

easy ecommerce

Write a query to return the ID, name, and price of every product, plus the running average price accumulated from the first product through that product in order of id.

easy ecommerce

Write a query to return the ID, status, and amount of every order, plus the running total of total_amount within the order's status, ordered by id.

medium ecommerce

Write a query to return the ID, customer ID, and total amount of every order, plus the running count of orders placed by that customer, accumulated in order of id.

medium ecommerce

Write a query to return the ID and amount of every order, plus the grand total of total_amount across every order and the running total accumulated through that order in order of id.

medium ecommerce

Write a query to return the ID, name, and price of every product, plus the running minimum and running maximum price observed from the first product through that product in order of id.

medium ecommerce

Write a query to return the ID, name, and price of every product, plus the running sum of price ordered by price ascending. Sort the final result by price ascending.

hard ecommerce

Write a query to return the ID and amount of every order, plus a running total of total_amount that adds exactly one order's amount per row, ordered by id.

hard ecommerce

Start learning to practice all 9 Aggregate Window Functions problems, with instant grading and mastery tracking.

Deeper guides on Aggregate Window Functions

Common questions about Aggregate Window Functions

Does a running total include the current row?

Yes. Each row shows the total of everything up to and including itself, so the first row equals its own value and the last equals the grand total. That is what makes the final row a useful check on the calculation.

Can you compute a running count as well as a running sum?

Yes. Any aggregate takes an OVER clause, so a count, an average or a minimum can all accumulate the same way. The ORDER BY inside OVER is what turns a whole-partition figure into an accumulating one.

Why is my running total the same on every row?

Because the OVER clause has no ORDER BY. Without one the aggregate covers the whole partition and every row gets the same grand total. Adding an ORDER BY inside the parentheses is what makes it accumulate.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.