How to Implement Window Functions for Cumulative Calculations in SQL
Use the SUM() aggregate with an OVER clause containing ORDER BY and ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW to compute running totals without collapsing rows.
Window functions are essential tools for data engineers who need to calculate progressive metrics like running totals, moving averages, and cumulative counts. The DataExpert-io/data-engineer-handbook repository provides practical, production-ready examples of how to implement window functions for cumulative calculations in real analytics scenarios.
The Core Pattern for Cumulative Sums
The fundamental syntax for cumulative calculations uses three components in the OVER clause:
ORDER BY— establishes the sequence for accumulationPARTITION BY(optional) — groups rows into independent windowsROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW— defines the sliding frame from the first row to the current row
This pattern appears throughout the repository's analytical patterns materials, particularly in intermediate-bootcamp/materials/4-applying-analytical-patterns/lecture-lab/window_based_analysis.sql.
Basic Cumulative Sum Example
SELECT
event_date,
sales,
SUM(sales) OVER (
ORDER BY event_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_sales
FROM daily_sales;
The ROWS clause explicitly bounds the window. UNBOUNDED PRECEDING starts from the first row in the partition, and CURRENT ROW includes the present row, producing a true running total.
Partitioned Cumulative Calculations
To calculate separate running totals per group—such as per-user metrics—add PARTITION BY:
SELECT
user_id,
event_date,
clicks,
SUM(clicks) OVER (
PARTITION BY user_id
ORDER BY event_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS user_running_clicks
FROM user_clicks;
Each user_id gets its own independent cumulative counter. This technique is demonstrated in the repository's user growth accounting examples, where user_growth_accounting.sql provides the source tables referenced in the window function analysis.
Beyond SUM: Other Cumulative Aggregates
Window functions support any standard aggregate for cumulative calculations. The AVG() function computes a running average using identical frame syntax:
SELECT
event_date,
revenue,
AVG(revenue) OVER (
ORDER BY event_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_avg_revenue
FROM daily_revenue;
Other valid aggregates include COUNT() for running row counts, MIN()/MAX() for cumulative extremes, and STRING_AGG() for concatenated histories (database-dependent).
Frame Specification Shortcuts
Most modern SQL engines recognize ROWS UNBOUNDED PRECEDING as shorthand for ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. The repository examples use the explicit form for clarity, but both are functionally equivalent:
-- Equivalent shorter syntax
SUM(sales) OVER (
ORDER BY event_date
ROWS UNBOUNDED PRECEDING
) AS running_sales
Source Files and Learning Path
The Data Engineer Handbook structures its window function materials in intermediate-bootcamp/materials/4-applying-analytical-patterns/:
| Path | Purpose |
|---|---|
lecture-lab/window_based_analysis.sql |
Complete walkthrough of cumulative calculations on user growth data |
tables/user_growth_accounting.sql |
DDL and sample data for the analysis tables |
README.md |
Conceptual overview of analytical patterns including window techniques |
homework/homework.md |
Reinforcement exercises for cumulative window logic |
Summary
- Use
ORDER BYin theOVERclause to control accumulation sequence - Add
PARTITION BYto compute independent cumulative totals per group - Specify
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWfor precise frame control - Apply any aggregate—
SUM,AVG,COUNT,MIN,MAX—to the same window frame - Reference the repository files
window_based_analysis.sqlanduser_growth_accounting.sqlfor production-tested patterns
Frequently Asked Questions
What does ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW mean?
This clause defines a sliding window frame that includes all rows from the beginning of the partition up to and including the current row. UNBOUNDED PRECEDING marks the first row in the partition (or table, if no PARTITION BY exists). CURRENT ROW includes the row being evaluated. Without this explicit frame, some SQL engines default to RANGE mode instead of ROWS, which can produce unexpected results with duplicate ordering values.
Can I use RANGE instead of ROWS for cumulative calculations?
ROWS counts physical row offsets. RANGE counts logical value differences based on the ORDER BY expression. For cumulative totals with unique ordering values, they behave identically. With duplicates in the ordering column, RANGE treats ties as a single group, while ROWS processes each row individually. The repository examples prefer ROWS for predictable, row-by-row accumulation.
How do I reset a cumulative sum at specific intervals?
Use PARTITION BY with a calculated period column. For monthly resetting cumulative totals:
SUM(sales) OVER (
PARTITION BY DATE_TRUNC('month', event_date)
ORDER BY event_date
ROWS UNBOUNDED PRECEDING
) AS monthly_running_sales
Each month becomes a separate partition with its own running total starting from zero.
Do window functions impact query performance significantly?
Window functions compute results in a single pass over the data after sorting, avoiding the self-joins or correlated subqueries required for equivalent results in older SQL. However, large partitions with complex ORDER BY operations may require significant memory. The explicit ROWS frame is generally faster than RANGE because it avoids peer-group detection logic. For performance-critical pipelines, test with your specific dataset and indexing strategy.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →