Using a correlated query:
SELECT t.id,
t.count,
(SELECT SUM(x.count)
FROM TABLE x
WHERE x.id <= t.id) AS cumulative_sum
FROM TABLE t
ORDER BY t.id
Using MySQL variables:
SELECT t.id,
t.count,
@running_total := @running_total + t.count AS cumulative_sum
FROM TABLE t
JOIN (SELECT @running_total := 0) r
ORDER BY t.id
Note:
- The
JOIN (SELECT @running_total := 0) ris a cross join, and allows for variable declaration without requiring a separateSETcommand. - The table alias,
r, is required by MySQL for any subquery/derived table/inline view
Caveats:
- MySQL specific; not portable to other databases
- The
ORDER BYis important; it ensures the order matches the OP and can have larger implications for more complicated variable usage (IE: psuedo ROW_NUMBER/RANK functionality, which MySQL lacks)
Using a correlated query:
SELECT t.id,
t.count,
(SELECT SUM(x.count)
FROM TABLE x
WHERE x.id <= t.id) AS cumulative_sum
FROM TABLE t
ORDER BY t.id
Using MySQL variables:
SELECT t.id,
t.count,
@running_total := @running_total + t.count AS cumulative_sum
FROM TABLE t
JOIN (SELECT @running_total := 0) r
ORDER BY t.id
Note:
- The
JOIN (SELECT @running_total := 0) ris a cross join, and allows for variable declaration without requiring a separateSETcommand. - The table alias,
r, is required by MySQL for any subquery/derived table/inline view
Caveats:
- MySQL specific; not portable to other databases
- The
ORDER BYis important; it ensures the order matches the OP and can have larger implications for more complicated variable usage (IE: psuedo ROW_NUMBER/RANK functionality, which MySQL lacks)
If performance is an issue, you could use a MySQL variable:
set @csum := 0;
update YourTable
set cumulative_sum = (@csum := @csum + count)
order by id;
Alternatively, you could remove the cumulative_sum column and calculate it on each query:
set @csum := 0;
select id, count, (@csum := @csum + count) as cumulative_sum
from YourTable
order by id;
This calculates the running sum in a running way :)
mysql - Creating a cumulative totals column - Database Administrators Stack Exchange
sql - Cumulative sum over a set of rows in mysql - Stack Overflow
Cumulative sum or running sum
sql - Creating a cumulative sum column in MySQL - Stack Overflow
UPDATE
MySQL 8.0 introduces "window functions", functionality equivalent to SQL Server "window functions" (with partitioning and ordering provided by Transact-SQL OVER syntax), and Oracle "analytic functions". It also now supports CTEs.
MySQL Reference Manual 12.21 Window Functions https://dev.mysql.com/doc/refman/8.0/en/window-functions.html
The answer provided here is an approach for MySQL versions prior to 8.0.
ORIGINAL ANSWER
MySQL doesn't provide the type analytic function you would use to get a running "cumulative sum", like the analytic functions available in other DBMS (like Oracle or SQL Server.)
But, it is possible to emulate some analytic functions, using MySQL.
There are (at least) two workable approaches:
One is to use a correlated subquery to get the subtotal. This approach can be expensive on large sets, and complicated if the predicates on the outer query are complicated. It really depends on how complicated that "multiple joins on multiple tables" is. (Unfortunately, MySQL also does not not support CTEs either.)
The other approach is to make use of MySQL user variables, to do some control break processing. The "trick" here is to the results from your query sorted (using an ORDER BY) and then wrapping your query in another query.
I'll give an example of the latter approach.
Because of the order that MySQL performs operations, the cumulative_total column needs to be computed before the value from id and day from the current row are saved into user variables. It's just easiest to put this column first.
The inline view aliased as i (in the query below) is just there to initialize the user variables, just in case these are already set in the session. If those already have values assigned, we want to ignore their current values, and the easiest way to do that is to initialize them.
Your original query gets wrapped in parenthesis, and is given an alias, c in the example below. The only change to your original query is the addition of an ORDER BY clause, so we can be sure that we process the rows from the query in sequence.
The outer select checks whether the id and day value from the current row "match" the previous row. If they do, we add the amount from the current row to the cumulative subtotal. If they don't match, then we reset the the cumulative subtotal to zero, and add the amount from the current row (or, more simply, just assign the amount from the current row).
After we have done the computation of the cumulative total, we save the id and day values from the current row into user variables, so they are available when we process the next row.
For example:
SELECT IF(@prev_id = c.id AND @prev_day = c.day
,@cumtotal := @cumtotal + c.amount
,@cumtotal := c.amount) AS cumulative_total
, @prev_id := c.id AS `id`
, @prev_day := c.day AS `day`
, c.hr
, c.amount AS `amount'
FROM ( SELECT @prev_id := NULL
, @prev_day := NULL
, @subtotal := 0
) i
JOIN (
select id, day, hr, amount from
( //multiple joins on multiple tables)a
left join
(//unions on multiple tables)b
on a.id=b.id
ORDER BY 1,2,3
) c
If it's necessary to return the columns in a different order, with cumulative total as the last column, then one option is to wrap that whole statement in a set of parens, and use that query as an inline view:
SELECT d.id
, d.day
, d.hr
, d.amount
, d.cumulative_total
FROM (
// query from above
) d
If you're on MySQL 8 or later, you should use window functions for this. Your query would read:
SELECT
id, day, hr, amount,
SUM (amount) OVER (PARTITION BY id, day ORDER BY hr) AS `cumulative total`
FROM t
Where t is your table b left joined to a. Some notes:
- The
PARTITION BYclause guarantees that you get a cumulative sum peridandday, so each day, we start summing afresh - The
ORDER BYclause defines by what ordering the cumulation should happen
Hello!
I am trying to write a SQL query using a combination of CTE and windows function to calculate a cumulative sum of the count of product.
My apologize as I'm using my mobile but here is an example of the dataset
Productcode RATE1 RATE1 RATE3 RATE1 RATE3 RATE2
The original table has a lot of columns but I'm only interested into 1. It also has customer id but a customer can only have 1 product. There is also date row but I'm not interested in the date
I wrote the below with the idea of taking a column out of many from the original dataset, group the product code and get individual count of each rate and finally add a column running a cumulative sum
WITH testtable AS (Select Productcode ,Count(*) as Freq From table group by 1) Select Productcode ,Sum(Freq) over (partition by productcode) as cumul From testtable
The output is essentially a duplicate of the column Freq as opposed to the cumulative Freq as I hoped.
I also appreciate that there is certainly an easier way of getting this done but I still would like to know why it is not working?
Thanks for your inputs!
Edit: moved 'AS' to the correct position Edit 2: sample dataset and further clarifications Edit 3: solved SUM(COLUMN) OVER (ORDER BY productid)
You can use a temporary variable to calculate the cumulative sum:
SELECT a.num,
(@s := @s + a.num) AS cumulative
FROM ID a, (SELECT @s := 0) dm
ORDER BY a.num;
I think I figured out the solution.
Select num as n,
(select sum(num) from ID where num <= n)
from ID order by n;