Skip to content

Window Aggregates

Standard aggregate functions (SUM, AVG, COUNT) can also be used as window functions. This allows you to calculate cumulative statistics (like a running total) or relative metrics (like % of total) without collapsing your data.

When you add an OVER() clause to an aggregate function, it changes from a “collapsing” operation to a “windowing” operation.

A Running Total is a cumulative sum of a column where each row shows the sum of all preceding values plus itself. To create a running total, you must include an ORDER BY inside the OVER() clause.

Running Total Logic
SUM(Amount) OVER(ORDER BY Date)

Detailed Explanation: Syntax and Comparison

Section titled “Detailed Explanation: Syntax and Comparison”

Requires ORDER BY. SUM(Sales) OVER(ORDER BY Month)

Requires an empty OVER(). SUM(Sales) OVER()

Requires PARTITION BY. SUM(Sales) OVER(PARTITION BY Region)

We have a DailyRevenue table:

DateRevenue
2023-01-01100
2023-01-02150
2023-01-03200

Task: Show daily revenue, the running total, and what % of the company’s total revenue that day represents.

Section titled “Task: Show daily revenue, the running total, and what % of the company’s total revenue that day represents.”
Mixed Windowing
SELECT
Date,
Revenue,
SUM(Revenue) OVER(ORDER BY Date) AS RunningTotal,
SUM(Revenue) OVER() AS GrandTotal,
(Revenue * 100.0) / SUM(Revenue) OVER() AS PctOfTotal
FROM DailyRevenue;
DateRevenueRunningTotalGrandTotalPctOfTotal
2023-01-0110010045022.2%
2023-01-0215025045033.3%
2023-01-0320045045044.4%

Why does ORDER BY turn a sum into a running total?

When you include an ORDER BY in a window aggregate, the database applies a default Window Frame. It tells the function to only look at: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

This means: “Start from the very first row of the partition and stop at the row I am currently processing.” Without the ORDER BY, the default frame is the entire partition.

If you need to calculate both a running total and a grand total, use two separate window functions as shown in the example. The database engine is smart enough to share the same table scan for both, making it highly efficient.