<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[mypublication]]></title><description><![CDATA[mypublication]]></description><link>https://theory-vs-reality.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>mypublication</title><link>https://theory-vs-reality.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 05:49:02 GMT</lastBuildDate><atom:link href="https://theory-vs-reality.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SQL Window Functions: Calculating Cumulative Sums with Unbounded Rows]]></title><description><![CDATA[A cumulative sum (or running total) continuously adds a sequence of numbers, providing a running aggregate at each step. In SQL, you calculate this using window functions combined with specific frame ]]></description><link>https://theory-vs-reality.hashnode.dev/sql-window-functions-calculating-cumulative-sums-with-unbounded-rows</link><guid isPermaLink="true">https://theory-vs-reality.hashnode.dev/sql-window-functions-calculating-cumulative-sums-with-unbounded-rows</guid><category><![CDATA[SQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[analytics]]></category><dc:creator><![CDATA[Dravonicx]]></dc:creator><pubDate>Sun, 06 Sep 2026 04:25:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a97d4259352a20af9ca2cca/7d95e33a-b4d3-4d43-b697-7998205b2cc9.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A cumulative sum (or running total) continuously adds a sequence of numbers, providing a running aggregate at each step. In SQL, you calculate this using window functions combined with specific frame clauses.</p>
<p>This guide explains the mechanics of window framing, explicitly detailing how to use the <code>UNBOUNDED PRECEDING</code> boundary to generate accurate running totals.</p>
<h2>The Mechanics of Window Framing</h2>
<p>When you use a window function like <code>SUM()</code> with an <code>ORDER BY</code> clause, SQL applies a default window frame to the calculation. A window frame defines exactly which rows to include relative to the current row.</p>
<p>To calculate a true running total from the beginning of a dataset up to the current row, the frame must span from the first row to the current row. SQL expresses this boundary concept as <code>UNBOUNDED PRECEDING</code>.</p>
<h2>Example 1: The Implicit Running Total</h2>
<p>If you include an <code>ORDER BY</code> inside the <code>OVER()</code> clause without explicitly defining the frame, standard SQL automatically applies a default frame: <code>RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code>.</p>
<p>Consider a table tracking daily sales:</p>
<pre><code class="language-sql">SELECT 
    sale_date,
    daily_revenue,
    SUM(daily_revenue) OVER (
        ORDER BY sale_date
    ) as cumulative_revenue
FROM daily_sales;
</code></pre>
<p>This query successfully calculates the running total. However, relying on the implicit <code>RANGE</code> default introduces a specific edge case involving duplicate sort values.</p>
<h2>Example 2: The Explicit ROWS Clause (Best Practice)</h2>
<p>The implicit <code>RANGE</code> behavior groups rows with identical sorting values (peers) together. If two sales occur on the exact same <code>sale_date</code>, the <code>RANGE</code> default calculates their combined total and applies that combined sum to both rows simultaneously.</p>
<p>To guarantee strict row-by-row addition regardless of duplicate sort keys, explicitly define the frame using <code>ROWS</code> instead of <code>RANGE</code>.</p>
<pre><code class="language-sql">SELECT 
    sale_date,
    transaction_id,
    daily_revenue,
    SUM(daily_revenue) OVER (
        ORDER BY sale_date, transaction_id
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) as exact_cumulative_revenue
FROM daily_sales;
</code></pre>
<blockquote>
<p><strong>💡 Developer Tip:</strong> Always use explicit <code>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code> for cumulative sums. It forces the database engine to process the sum strictly row-by-row, eliminating unexpected grouping artifacts caused by duplicate order keys. Furthermore, <code>ROWS</code> calculations generally require less memory overhead than <code>RANGE</code> calculations.</p>
</blockquote>
<h2>Example 3: Partitioning the Cumulative Sum</h2>
<p>Data engineering often requires running totals that reset at specific boundaries, such as calculating a monthly running total rather than an all-time running total.</p>
<p>You achieve this by adding a <code>PARTITION BY</code> clause before the <code>ORDER BY</code>. The <code>UNBOUNDED PRECEDING</code> frame then applies only within the boundaries of that specific partition.</p>
<pre><code class="language-sql">SELECT 
    customer_id,
    EXTRACT(MONTH FROM sale_date) as sale_month,
    sale_date,
    daily_revenue,
    SUM(daily_revenue) OVER (
        PARTITION BY customer_id, EXTRACT(MONTH FROM sale_date)
        ORDER BY sale_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) as monthly_running_total
FROM daily_sales;
</code></pre>
<p>In this query, the cumulative sum begins at the first transaction of the month for a specific customer. When the month or the customer changes, the <code>UNBOUNDED PRECEDING</code> boundary resets, restarting the sum from zero.</p>
<h2>Summary</h2>
<p>Window functions provide the most efficient mechanism for calculating cumulative sums in relational databases. By explicitly defining the window frame as <code>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code>, you ensure precise, deterministic running totals that avoid the logical artifacts of default <code>RANGE</code> behaviors.</p>
]]></content:encoded></item></channel></rss>