How to Generate a Running(Accumulated) Total in SQL

Have you ever needed to calculate a “running total” or “cumulative sum” in SQL?

You can do this in SQL without any other programming language to process the data.

What is a Running Total?

A running total, or cumulative sum, is a value that indicates the total of a value in the current row and all rows above it.

It can be used across a span of time, to find the total order value so far for an eCommerce site. Or, it could be used in a sports website to display the team’s score so far for every score that is made.

There are many uses for it, and in this guide we’ll see how to calculate it.

Sample Data

We’ll use some sample data to demonstrate the concept of an SQL running total.

Here’s our sample table called sales_history, which represents orders and the value of orders that have been made over time.

order_idsales_datesalespersonorder_value
101 Aug 2021John15
201 Aug 2021Sarah8
301 Aug 2021Sally19
402 Aug 2021John2
502 Aug 2021Mark18
603 Aug 2021Sally3
703 Aug 2021Mark21
803 Aug 2021Sarah16
904 Aug 2021John4

Here’s the SQL to create the table. This will work in MySQL but you can adjust the data types for other databases.

CREATE TABLE sales_history (
  order_id INT,
  sales_date DATE,
  salesperson VARCHAR(20),
  order_value INT
);

 

INSERT INTO sales_history
(order_id, sales_date, salesperson, order_value) VALUES
(1, '20210801', 'John', 15),
(2, '20210801', 'Sarah', 8),
(3, '20210801', 'Sally', 19),
(4, '20210802', 'John', 2),
(5, '20210802', 'Mark', 18),
(6, '20210803', 'Sally', 3),
(7, '20210803', 'Mark', 21),
(8, '20210803', 'Sarah', 16),
(9, '20210804', 'John', 4);

If we want to select data from this table, our query would look like this:

SELECT
order_id,
sales_date,
salesperson,
order_value
FROM sales_history;

How can we generate a running total?

Generate a Running Total in SQL with a Window Function

To generate a running total in SQL, we can use something called a “window function”.

A window function is a way to write a function so it looks over a range of records, or a “window”, instead of all records or a single record.

In this case, we can use a window function to look at the SUM of the number of sales. Our “window” or range of data to look at would be all of the rows that have come before this.

For our example, we can use the SUM function to calculate the running total.

The SUM function, written as a window function, looks like this:

SUM (expression)
OVER (
  [ PARTITION BY partition_clause ]
  [ ORDER BY order_clause ]
)

This has a few components:

  • The expression is the column we want to sum.
  • The partition_clause specifies the range of data to use
  • The order_clause specifies how data is ordered in the range

Let’s see an example of this.

SELECT
order_id,
sales_date,
salesperson,
order_value,
SUM(order_value)
  OVER (ORDER BY order_id ASC) AS running_total
FROM sales_history
ORDER BY order_id ASC;

Run this query, and you’ll see these results:

order_idsales_datesalespersonorder_valuerunning_total
101 Aug 2021John1515
201 Aug 2021Sarah823
301 Aug 2021Sally1942
402 Aug 2021John244
502 Aug 2021Mark1862
603 Aug 2021Sally365
703 Aug 2021Mark2186
803 Aug 2021Sarah16102
904 Aug 2021John4106

Awesome! We’ve used the SUM function as a window function to calculate the running total.

Generating a Running Total In Groups

Another scenario for generating a running total is to see a running total in different groups.

Perhaps you want to see a running total of sales per month, or scores in a match by each person, or something else.

You can do this with a slight modification of the SUM function.

In the OVER clause, another clause to add is the partition by clause. This allows us to specify groups when calculating the SUM value.

This means, for our data, we could see the running total of orders for each salesperson.

To do this, we partition by the salesperson in our query:

SELECT
order_id,
sales_date,
salesperson,
order_value,
SUM(order_value) OVER (
  PARTITION BY salesperson
  ORDER BY order_id ASC) AS running_total
FROM sales_history
ORDER BY order_id ASC;

If we run this query, this is what we’ll see:

order_idsales_datesalespersonorder_valuerunning_total
101 Aug 2021John1515
201 Aug 2021Sarah88
301 Aug 2021Sally1919
402 Aug 2021John217
502 Aug 2021Mark1818
603 Aug 2021Sally322
703 Aug 2021Mark2139
803 Aug 2021Sarah1624
904 Aug 2021John421

Filter on a Running Total

Another scenario you might see when working with running totals is the need to filter on it.

How can you filter on this data? Let’s say you want to see all orders where the running total is greater than 20.

We could try this query, using the WHERE clause:

We leave the ORDER BY on the outside of the query as that’s how the data is ordered.

Now, we can add a where clause on the running total, because here it’s a derived column (from the subquery) and not a window function.

SELECT
order_id,
sales_date,
salesperson,
order_value,
running_total
FROM (
  SELECT
  order_id,
  sales_date,
  salesperson,
  order_value,
  SUM(order_value) OVER (
    PARTITION BY salesperson
    ORDER BY order_id ASC) AS running_total
  FROM sales_history
) sub
WHERE running_total > 20
ORDER BY salesperson ASC, order_id ASC;

Run this query and this is the result we’ll see:

order_idsales_datesalespersonorder_valuerunning_total
904 Aug 2021John421
703 Aug 2021Mark2139
603 Aug 2021Sally322
803 Aug 2021Sarah1624

We’ve filtered the results based on the running total value by enclosing the query inside a subquery and putting the WHERE clause outside the subquery.

Only those rows with a running total value of greater than 20 are shown.

Conclusion

So, that’s how to calculate a running total in SQL. This technique (using SUM as a window function) should work in Oracle, SQL Server, MySQL, and PostgreSQL, and many other databases too.

credit: https://www.databasestar.com/sql-running-total