Running Totals and Moving Averages: Simple Examples
11a - RUNNING TOTALS and MOVING AVERAGES (VERY SIMPLE version)
---------------------------------------------------------------------
No setup needed. This file creates its own small table.
- Running total: add up all rows from the start until this row SUM(Amount) OVER (ORDER BY Day ROWS UNBOUNDED PRECEDING)
Moving average : average of this row and the few rows before it
AVG(Amount) OVER (ORDER BY Day ROWS 2 PRECEDING) -> last 3 rows
Run the sample database script first. Every query below was run on that data, and the Output under each query is the real result.
IF DB_ID('SqlLearningDb') IS NULL
CREATE DATABASE SqlLearningDb;
GO
USE SqlLearningDb;
GO
Small table: daily sales of 2 shops for 5 days
DROP TABLE IF EXISTS dbo.DailyShopSales;
CREATE TABLE dbo.DailyShopSales
(
ShopName VARCHAR(10),
SaleDay INT, -- day 1, 2, 3 ...
Amount INT
);
INSERT INTO dbo.DailyShopSales (ShopName, SaleDay, Amount) VALUES
('ShopA', 1, 100),
('ShopA', 2, 200),
('ShopA', 3, 150),
('ShopA', 4, 300),
('ShopA', 5, 250),
('ShopB', 1, 50),
('ShopB', 2, 80),
('ShopB', 3, 70),
('ShopB', 4, 120),
('ShopB', 5, 90);
GO
SELECT * FROM dbo.DailyShopSales;
Messages(10 rows affected)
Output
| ShopName | SaleDay | Amount |
| ShopA | 1 | 100 |
| ShopA | 2 | 200 |
| ShopA | 3 | 150 |
| ShopA | 4 | 300 |
| ShopA | 5 | 250 |
| ShopB | 1 | 50 |
| ShopB | 2 | 80 |
| ShopB | 3 | 70 |
| ShopB | 4 | 120 |
| ShopB | 5 | 90 |
Running total for ShopA
Day 1 = 100, Day 2 = 100+200 = 300, Day 3 = 300+150 = 450 ...
SELECT SaleDay, Amount,
SUM(Amount) OVER (ORDER BY SaleDay ROWS UNBOUNDED PRECEDING) AS RunningTotal
FROM dbo.DailyShopSales
WHERE ShopName = 'ShopA';
Output
| SaleDay | Amount | RunningTotal |
| 1 | 100 | 100 |
| 2 | 200 | 300 |
| 3 | 150 | 450 |
| 4 | 300 | 750 |
| 5 | 250 | 1000 |
Running total for Each shop (starts again for ShopB)
SELECT ShopName, SaleDay, Amount,
SUM(Amount) OVER (PARTITION BY ShopName ORDER BY SaleDay ROWS UNBOUNDED PRECEDING) AS RunningTotal
FROM dbo.DailyShopSales;
Output
| ShopName | SaleDay | Amount | RunningTotal |
| ShopA | 1 | 100 | 100 |
| ShopA | 2 | 200 | 300 |
| ShopA | 3 | 150 | 450 |
| ShopA | 4 | 300 | 750 |
| ShopA | 5 | 250 | 1000 |
| ShopB | 1 | 50 | 50 |
| ShopB | 2 | 80 | 130 |
| ShopB | 3 | 70 | 200 |
| ShopB | 4 | 120 | 320 |
| ShopB | 5 | 90 | 410 |
Running count: how many days so far
SELECT ShopName, SaleDay, Amount,
COUNT(*) OVER (PARTITION BY ShopName ORDER BY SaleDay ROWS UNBOUNDED PRECEDING) AS DaysSoFar
FROM dbo.DailyShopSales;
Output
| ShopName | SaleDay | Amount | DaysSoFar |
| ShopA | 1 | 100 | 1 |
| ShopA | 2 | 200 | 2 |
| ShopA | 3 | 150 | 3 |
| ShopA | 4 | 300 | 4 |
| ShopA | 5 | 250 | 5 |
| ShopB | 1 | 50 | 1 |
| ShopB | 2 | 80 | 2 |
| ShopB | 3 | 70 | 3 |
| ShopB | 4 | 120 | 4 |
| ShopB | 5 | 90 | 5 |
Moving average of the last 3 days (this day + 2 days before)
"Amount * 1.0" keeps decimals (AVG of whole numbers drops them)
SELECT SaleDay, Amount,
AVG(Amount * 1.0) OVER (ORDER BY SaleDay ROWS 2 PRECEDING) AS MovingAvg3Days
FROM dbo.DailyShopSales
WHERE ShopName = 'ShopA';
Output
| SaleDay | Amount | MovingAvg3Days |
| 1 | 100 | 100.000000 |
| 2 | 200 | 150.000000 |
| 3 | 150 | 150.000000 |
| 4 | 300 | 216.666666 |
| 5 | 250 | 233.333333 |
Moving SUM of the last 2 days (this day + 1 day before)
SELECT SaleDay, Amount,
SUM(Amount) OVER (ORDER BY SaleDay ROWS 1 PRECEDING) AS Last2DaysTotal
FROM dbo.DailyShopSales
WHERE ShopName = 'ShopA';
Output
| SaleDay | Amount | Last2DaysTotal |
| 1 | 100 | 100 |
| 2 | 200 | 300 |
| 3 | 150 | 350 |
| 4 | 300 | 450 |
| 5 | 250 | 550 |
Highest sale so far (running MAX)
SELECT SaleDay, Amount,
MAX(Amount) OVER (ORDER BY SaleDay ROWS UNBOUNDED PRECEDING) AS BestDaySoFar
FROM dbo.DailyShopSales
WHERE ShopName = 'ShopA';
Output
| SaleDay | Amount | BestDaySoFar |
| 1 | 100 | 100 |
| 2 | 200 | 200 |
| 3 | 150 | 200 |
| 4 | 300 | 300 |
| 5 | 250 | 300 |
Practice Questions
- Show the running total of ShopB only.
- Show a moving average of the last 2 days for each shop.
- Show the LOWEST sale so far for each shop (running MIN).