{"id":"l-sql-3-3","courseId":"sql-for-analysts","lang":"en","kind":"lesson","summary":"Turn a monthly or daily table into a trend with running totals, year-to-date resets, LAG and LEAD comparisons, and moving averages over explicit ROWS frames, including filling missing days first.","html":"<p>Rania’s monthly business review needs three things on one slide: revenue by month, how much of the year’s total has accumulated so far, and whether each month grew on the last. You could compute the year-to-date figure in a spreadsheet afterwards. But the same window functions that ranked products last lesson can also <em>accumulate</em> and <em>compare</em>, so the whole table comes straight out of SQL, reproducibly.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Running totals and year-to-date</h2>\n<p>A window aggregate with an <code dir=\"ltr\">ORDER BY</code> inside <code dir=\"ltr\">OVER</code> becomes a running calculation: each row sees the rows up to itself. Add <code dir=\"ltr\">PARTITION BY</code> and it restarts per group. Here are Cartwheel’s monthly orders with a year-to-date count that resets each January:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> strftime(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'%Y-%m'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    PARTITION</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> substr(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_ytd</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 14</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>The partition key is the year, taken from the first four characters of the month label. December 2024 closes the year at 552 orders, and January 2025 starts again at 84.</p>\n<p>Two windows on the same row combine nicely. Dividing the running total by the total of the whole partition, <code dir=\"ltr\">100.0 * SUM(orders) OVER (PARTITION BY substr(month, 1, 4) ORDER BY month) / SUM(orders) OVER (PARTITION BY substr(month, 1, 4))</code>, gives the cumulative share of the year reached by each month. For a seasonal business like Cartwheel, that’s the number finance actually tracks: by the end of October 2025, the year was just under two-thirds done.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Forgetting ORDER BY inside OVER</span></p>\n<p><code dir=\"ltr\">SUM(orders) OVER ()</code> and <code dir=\"ltr\">SUM(orders) OVER (PARTITION BY year)</code> don’t produce running totals; every row gets the full total of its window. It’s the ORDER BY inside the parentheses that makes the window grow row by row. If every row of your “running total” shows the same number, that’s the missing piece.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">Comparing with the previous row</h2>\n<p><code dir=\"ltr\">LAG(x)</code> returns the value of <code dir=\"ltr\">x</code> from the previous row in the window order, and <code dir=\"ltr\">LEAD(x)</code> from the next. Period-over-period change is then simple arithmetic:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"1\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> strftime(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'%Y-%m'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> prev_month,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> change,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        /</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> NULLIF</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> change_pct,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> same_month_last_year</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> DESC</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>November 2025 jumped by 193 orders, then December fell by 140: Black Friday, then the hangover. Month-over-month numbers are noisy for a seasonal business, which is why the last column uses <code dir=\"ltr\">LAG(orders, 12)</code>, the value twelve rows back, for a year-over-year view: 325 orders this November against 120 last November.</p>\n<p><code dir=\"ltr\">LAG(orders, 12)</code> is only “the same month last year” if every month has a row. A month with no orders would have no row, and every comparison after it would silently shift by one month. That assumption is about to matter.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Moving averages and the frame</h2>\n<p>A moving average smooths daily noise. The window needs an explicit <strong>frame</strong>, the range of rows around the current one that the aggregate sees: <code dir=\"ltr\">ROWS BETWEEN 6 PRECEDING AND CURRENT ROW</code> is the current row plus the six before it.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 170\" role=\"img\" aria-labelledby=\"l-sql-3-3-t1\"><defs><marker id=\"arrow-l-sql-3-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sql-3-3-t1\">Ten daily rows in order. For the current row, day 9, the frame ROWS BETWEEN 6 PRECEDING AND CURRENT ROW covers days 3 through 9, seven rows. When the current row moves forward one day, the frame moves with it.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"50\" y=\"85\" text-anchor=\"middle\">d1</text>\n  <rect class=\"d-box\" x=\"88\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"118\" y=\"85\" text-anchor=\"middle\">d2</text>\n  <rect class=\"d-box-accent\" x=\"156\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"186\" y=\"85\" text-anchor=\"middle\">d3</text>\n  <rect class=\"d-box-accent\" x=\"224\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"254\" y=\"85\" text-anchor=\"middle\">d4</text>\n  <rect class=\"d-box-accent\" x=\"292\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"322\" y=\"85\" text-anchor=\"middle\">d5</text>\n  <rect class=\"d-box-accent\" x=\"360\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"390\" y=\"85\" text-anchor=\"middle\">d6</text>\n  <rect class=\"d-box-accent\" x=\"428\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"458\" y=\"85\" text-anchor=\"middle\">d7</text>\n  <rect class=\"d-box-accent\" x=\"496\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"526\" y=\"85\" text-anchor=\"middle\">d8</text>\n  <rect class=\"d-box-primary\" x=\"564\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-label-strong\" x=\"594\" y=\"85\" text-anchor=\"middle\">d9</text>\n  <rect class=\"d-box\" x=\"632\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"662\" y=\"85\" text-anchor=\"middle\">d10</text>\n  <path class=\"d-line\" d=\"M156 120 L624 120\"/>\n  <path class=\"d-line\" d=\"M156 112 L156 128\"/>\n  <path class=\"d-line\" d=\"M624 112 L624 128\"/>\n  <text class=\"d-label\" x=\"390\" y=\"150\" text-anchor=\"middle\">frame: 6 PRECEDING to CURRENT ROW (7 rows)</text>\n  <text class=\"d-label-muted\" x=\"594\" y=\"40\" text-anchor=\"middle\">current row</text>\n</svg><figcaption>A ROWS frame slides along the ordered rows</figcaption></figure>\n<p>The frame counts <strong>rows</strong>, not days. If a day has no orders, it has no row, and the frame quietly stretches further back in time. Cartwheel’s first month shows how badly that can mislead: in January 2024 only 8 orders arrived, on 8 different days. A 7-row average over the existing rows says “1.0 orders a day” all month. The truth is about a quarter of that.</p>\n<p>The fix is a calendar: generate one row per day with a recursive CTE, LEFT JOIN the daily counts, and turn missing days into zeros:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"2\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> RECURSIVE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  UNION ALL</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'+1 day'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-31'</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">daily </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-02-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filled </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COALESCE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">orders</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  LEFT JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> daily </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> x </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">smoothed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">AVG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">           ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ROWS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> PRECEDING</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> CURRENT </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ROW</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">         ), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_7d_avg</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> filled</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> smoothed</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-25'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>Now 31 January shows 0.43 orders a day over the last week, the honest number. Notice where the date filter sits: in the final SELECT, <em>after</em> the moving average is computed in <code dir=\"ltr\">smoothed</code>. Window functions run after WHERE, so filtering in the same query would leave the first displayed days with no history to average. The same calendar trick protects <code dir=\"ltr\">LAG(orders, 12)</code> from missing months.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>ROWS, not the default</span></p>\n<p>With an ORDER BY and no frame, a window aggregate uses <code dir=\"ltr\">RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code>, which treats rows with equal sort values as one step: all of them get the same running total. When the sort key isn’t unique, write <code dir=\"ltr\">ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code> to get a strict row-by-row running total.</p>\n</aside>\n<p>You can now rank, accumulate and compare. The next lesson puts these tools to work on the two analyses every product team asks for: funnels and cohort retention.</p>\n","toc":[{"id":"sec-1","text":"Running totals and year-to-date","level":2},{"id":"sec-2","text":"Comparing with the previous row","level":2},{"id":"sec-3","text":"Moving averages and the frame","level":2}],"takeaways":["SUM(x) OVER (ORDER BY period) is a running total; adding PARTITION BY year makes it reset each year, which is year-to-date.","LAG(x) reads the previous row’s value and LEAD(x) the next one, so period-over-period change is x - LAG(x).","A moving average needs an explicit frame, such as ROWS BETWEEN 6 PRECEDING AND CURRENT ROW, and a row for every period, including empty ones.","Window functions run after WHERE, so filter the displayed range in an outer query or the window will only see the filtered rows."],"further":[{"title":"SQLite window functions, frame specifications","url":"https://www.sqlite.org/windowfunctions.html#frame_specifications"},{"title":"PostgreSQL window function calls","url":"https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS"}],"quiz":[{"q":"<p><code dir=\"ltr\">SUM(revenue) OVER ()</code> is added to a monthly revenue table. What does every row show?</p>","options":[{"text":"The running total up to that month.","why":"A running total needs an ORDER BY inside OVER. Without it, the window is the whole result."},{"text":"The previous month’s revenue.","why":"That’s LAG(revenue) OVER (ORDER BY month)."},{"text":"That month’s revenue, unchanged.","why":"A window SUM adds up every row in the window, which here is all rows, not just the current one."},{"text":"The grand total of all months.","why":"Correct. An empty OVER () makes one window containing every row, so each row gets the same total."}],"answer":3},{"q":"<p>Monthly orders are 84, 63 and 70 for January to March. What does <code dir=\"ltr\">orders - LAG(orders) OVER (ORDER BY month)</code> return for January, February and March?</p>","options":[{"text":"0, -21, 7","why":"LAG has no previous row for January, so it returns NULL, and so does the subtraction, not 0."},{"text":"-21, 7, NULL","why":"That’s the LEAD-based version, comparing each month with the next one."},{"text":"NULL, -21, 7","why":"Correct. January has no previous row; February is 63 - 84; March is 70 - 63. Pass a default, <code dir=\"ltr\">LAG(orders, 1, 0)</code>, only if 0 is truly the right baseline."}],"answer":2},{"q":"<p>Daily orders are missing on days with no sales. What’s wrong with <code dir=\"ltr\">AVG(orders) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)</code> on that table?</p>","options":[{"text":"The frame covers the last 7 rows, which can span far more than 7 days, so quiet days are ignored and the average is too high.","why":"Correct. ROWS counts rows, not dates. Fill the calendar with a row for every day (0 orders) before averaging."},{"text":"AVG can’t be used as a window function.","why":"Every aggregate, AVG included, can be used with OVER."},{"text":"ROWS frames are not supported in SQLite.","why":"SQLite has supported ROWS frames since window functions arrived in version 3.25 (2018)."}],"answer":0},{"q":"<p>This query should show a 7-day moving average for the last week of December only. Why are the first values too low?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">AVG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ROWS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> PRECEDING</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> CURRENT </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ROW</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> filled_days</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-12-25'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n","options":[{"text":"The frame should be RANGE, not ROWS.","why":"The frame type isn’t the issue. The earlier days the frame needs were removed by WHERE before the window ran, and no frame can see rows that aren’t there."},{"text":"AVG ignores days with 0 orders.","why":"AVG ignores NULLs, not zeros. Zeros are averaged in like any other number."},{"text":"The ORDER BY should be descending.","why":"Descending order would make the frame look forward in time, which isn’t a trailing average."},{"text":"WHERE runs before the window, so for 25 December the frame has no earlier days to average over.","why":"Correct. Compute the moving average over the full range in a CTE, then filter the days you want to display in the outer query."}],"answer":3}],"exercise":{"kind":"sql","starter":"-- 2025 monthly revenue: running total and change vs previous month\nWITH monthly AS (\n  SELECT\n    strftime('%Y-%m', o.order_date) AS month,\n    SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS revenue\n  FROM orders AS o\n  JOIN order_items AS oi ON oi.order_id = o.order_id\n  WHERE o.status <> 'cancelled'\n    AND o.order_date >= '2025-01-01'\n    AND o.order_date < '2026-01-01'\n  GROUP BY month\n)\nSELECT\n  month,\n  ROUND(revenue, 2) AS revenue,\n  ROUND(SUM(revenue) OVER (), 2) AS running_revenue,\n  ROUND(revenue - LEAD(revenue) OVER (ORDER BY month), 2) AS mom_change\nFROM monthly\nORDER BY month;","solution":"WITH monthly AS (\n  SELECT\n    strftime('%Y-%m', o.order_date) AS month,\n    SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS revenue\n  FROM orders AS o\n  JOIN order_items AS oi ON oi.order_id = o.order_id\n  WHERE o.status <> 'cancelled'\n    AND o.order_date >= '2025-01-01'\n    AND o.order_date < '2026-01-01'\n  GROUP BY month\n)\nSELECT\n  month,\n  ROUND(revenue, 2) AS revenue,\n  ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_revenue,\n  ROUND(revenue - LAG(revenue) OVER (ORDER BY month), 2) AS mom_change\nFROM monthly\nORDER BY month;","orderMatters":true,"title":"Rania's revenue trend","prompt":"<p>Build the slide table for Rania: one row per month of 2025 (non-cancelled orders) with <code dir=\"ltr\">month</code> (‘YYYY-MM’), <code dir=\"ltr\">revenue</code>, <code dir=\"ltr\">running_revenue</code> (revenue accumulated from January up to and including that month) and <code dir=\"ltr\">mom_change</code> (this month’s revenue minus the previous month’s; NULL for January). Round the money columns to 2 decimals and sort by <code dir=\"ltr\">month</code>. Order matters.</p>\n<p>The <code dir=\"ltr\">monthly</code> CTE is correct. Both window columns in the final SELECT are wrong.</p>\n","hints":["Every row of <code dir=\"ltr\">running_revenue</code> shows the same number. What’s missing inside <code dir=\"ltr\">OVER ()</code>?","Use <code dir=\"ltr\">SUM(revenue) OVER (ORDER BY month)</code> for the running total.","LEAD looks at the next row. To compare with the previous month, use <code dir=\"ltr\">revenue - LAG(revenue) OVER (ORDER BY month)</code>."],"explanation":"<p>An empty <code dir=\"ltr\">OVER ()</code> makes one window containing all twelve months, so the starter printed the annual total on every row. Adding <code dir=\"ltr\">ORDER BY month</code> turns it into a growing window, which is a running total. For month-over-month change you need the previous row, which is LAG; LEAD compared each month with the following one and left December NULL instead of January. December’s running revenue equals the year’s total, a free reconciliation check against the category table from the joins lesson.</p>\n","checkLabels":{}},"words":704,"readMinutes":3}