{"id":"l-sql-4-4","courseId":"sql-for-analysts","lang":"en","kind":"lesson","summary":"Combine the whole course in one analysis, comparing Q4 2025 with Q4 2024 by country through named CTE steps, reconciling the totals, stating caveats and writing the answer a CEO can act on.","html":"<p>The last request of the year comes from Rania herself: “I’m presenting the Q4 review to the board. How did Q4 2025 compare with Q4 2024, country by country, and what’s the story?” This is the whole course in one question. It needs date ranges, joins without fan-out, conditional aggregation, CTEs, sanity checks and, at the end, a few sentences a board can act on. Work through it the way you would at a real desk: definitions first, then steps, then checks, then words.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Step 1: fix the definitions</h2>\n<p>Before any SQL, write down what each word in the question means, and put it where the query can see it:</p>\n<ul>\n<li><strong>Q4</strong>: 1 October to 31 December, filtered as half-open ranges, from the first day of the quarter up to but not including 1 January, for both 2024 and 2025.</li>\n<li><strong>Revenue</strong>: sum of <code dir=\"ltr\">quantity * unit_price * (1 - discount)</code> from <code dir=\"ltr\">order_items</code>, shipping excluded.</li>\n<li><strong>Which orders</strong>: every status except ‘cancelled’, the definition used throughout this course.</li>\n<li><strong>Country</strong>: the customer’s country, from <code dir=\"ltr\">customers</code>.</li>\n<li><strong>Grain of the answer</strong>: one row per country.</li>\n</ul>\n<p>The periods go into a tiny CTE of their own. Every later step joins to it, so changing the quarter means editing one place, not six.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Step 2: build it in named steps</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 210\" role=\"img\" aria-labelledby=\"l-sql-4-4-t1\"><defs><marker id=\"arrow-l-sql-4-4-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-4-4-t1\">periods (one row per quarter) and order_revenue (one row per order) feed q4_orders (one row per Q4 order), which feeds by_country (one row per country), which feeds the final SELECT that adds growth and average order value.</title>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"30\" width=\"160\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"100\" y=\"52\" text-anchor=\"middle\">periods</text>\n  <text class=\"d-label-muted\" x=\"100\" y=\"71\" text-anchor=\"middle\">1 row / quarter</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"130\" width=\"160\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"100\" y=\"152\" text-anchor=\"middle\">order_revenue</text>\n  <text class=\"d-label-muted\" x=\"100\" y=\"171\" text-anchor=\"middle\">1 row / order</text>\n  <rect class=\"d-box-primary\" x=\"230\" y=\"80\" width=\"150\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"305\" y=\"102\" text-anchor=\"middle\">q4_orders</text>\n  <text class=\"d-label-muted\" x=\"305\" y=\"121\" text-anchor=\"middle\">1 row / Q4 order</text>\n  <rect class=\"d-box-primary\" x=\"420\" y=\"80\" width=\"140\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"490\" y=\"102\" text-anchor=\"middle\">by_country</text>\n  <text class=\"d-label-muted\" x=\"490\" y=\"121\" text-anchor=\"middle\">1 row / country</text>\n  <rect class=\"d-box-success\" x=\"600\" y=\"80\" width=\"105\" height=\"50\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"652\" y=\"102\" text-anchor=\"middle\">SELECT</text>\n  <text class=\"d-label-muted\" x=\"652\" y=\"121\" text-anchor=\"middle\">growth</text>\n  <path class=\"d-arrow\" d=\"M180 60 L228 95\" marker-end=\"url(#arrow-l-sql-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M180 150 L228 115\" marker-end=\"url(#arrow-l-sql-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M380 105 L418 105\" marker-end=\"url(#arrow-l-sql-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M560 105 L598 105\" marker-end=\"url(#arrow-l-sql-4-4-1)\"/>\n</svg><figcaption>The capstone query as a pipeline of CTEs, each with its own grain</figcaption></figure>\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\"> periods </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\"> 'Q4 2024'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> period</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2024-10-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> start_date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-01-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> end_date</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:#0A3069;--shiki-dark:#A5D6FF\"> 'Q4 2025'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-10-01'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2026-01-01'</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\">order_revenue </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:#6E7781;--shiki-dark:#8B949E\">  -- grain: one row per order</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_id, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(quantity </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> unit_price </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> discount)) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> revenue</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_items</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_id</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\">q4_orders </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:#6E7781;--shiki-dark:#8B949E\">  -- grain: one row per non-cancelled Q4 order</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">period</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">country</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">r</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">revenue</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> o</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> periods </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">start_date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">end_date</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_revenue </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> r </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> r</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">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:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_country </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:#6E7781;--shiki-dark:#8B949E\">  -- grain: one row per country</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    country,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FILTER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> period</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Q4 2024'</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\"> revenue_2024,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FILTER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> period</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Q4 2025'</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\"> revenue_2025,</span></span>\n<span class=\"line\"><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\">FILTER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> period</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Q4 2025'</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_2025</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> q4_orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> country</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:#1F2328;--shiki-dark:#E6EDF3\">  country,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue_2024, </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\"> revenue_q4_2024,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue_2025, </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\"> revenue_q4_2025,</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\"> (revenue_2025 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> revenue_2024) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> NULLIF</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue_2024, </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\"> growth_pct,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue_2025 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_2025, </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\"> avg_order_value_2025</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_country</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> revenue_q4_2025 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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>Every technique here has a lesson behind it. The join to <code dir=\"ltr\">periods</code> uses a range condition in ON, which labels each order with its quarter and drops everything else. <code dir=\"ltr\">order_revenue</code> collapses items first, so <code dir=\"ltr\">COUNT(*)</code> later counts orders. <code dir=\"ltr\">FILTER</code> puts both years side by side in one pass. <code dir=\"ltr\">NULLIF</code> protects the growth calculation from a country with no sales last year.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Step 3: check before you believe</h2>\n<p>Two checks take a minute and catch most mistakes. First, <strong>reconcile</strong>: the eight <code dir=\"ltr\">revenue_q4_2025</code> values should add up to the total from a simpler query over <code dir=\"ltr\">orders</code> and <code dir=\"ltr\">order_items</code> alone, 146,825.44, and the 2024 column to 52,537.10. They do, to within a cent of rounding, so no fan-out or lost rows. Second, <strong>revisit the data-quality suite</strong>. The misfiled products don’t matter at country level. Two returned orders without return records and 63 orders still ‘processing’ or ‘shipped’ do: some Q4 2025 revenue may yet be cancelled or refunded.</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>Reporting a percentage without its base</span></p>\n<p>Jordan grew 627%, the biggest number on the slide, from about 790 to 5,760. Put that percentage alone in a headline and the board will ask about Jordan for twenty minutes. Always show growth next to the absolute values, and call out small bases explicitly.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Step 4: write the answer</h2>\n<p>The query is not the deliverable; the answer is. Here’s what goes to Rania:</p>\n<blockquote>\n<p>Q4 2025 revenue was 146.8k, up from 52.5k in Q4 2024 (+179%), with every country at least doubling. The growth is volume, not basket size: orders rose from 228 to 642 while average order value held at about 230. The UK is now our largest market (26.5k, up from 5.4k). Jordan’s +627% comes from a small base. Caveat: 63 Q4 orders, most of them from December, were still processing or in transit at year end, so cancellations and returns could trim Q4 slightly.</p>\n</blockquote>\n<p>Three to five sentences, the numbers that matter, one honest caveat. Notice what it doesn’t say: <em>why</em> volume grew. The cohort lesson suggests where to look next, at how many of those orders came from new customers acquired in the Black Friday weeks. A good answer usually ends by pointing at the next good question.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">What you can do now</h2>\n<p>Look back at the first lesson’s request about thin margins. You’ve gone from SELECT and LIMIT to an analysis with five named steps, built-in checks and a written conclusion. More importantly, you’ve built habits: write the question and grain before the query, use half-open date ranges, put right-table filters in ON, aggregate before joining, use NOT EXISTS for “never”, reconcile every total, and treat NULLs and failed checks as questions. Those habits transfer to any database, any dialect and any business. The exercise below is your last one, written from a blank page.</p>\n","toc":[{"id":"sec-1","text":"Step 1: fix the definitions","level":2},{"id":"sec-2","text":"Step 2: build it in named steps","level":2},{"id":"sec-3","text":"Step 3: check before you believe","level":2},{"id":"sec-4","text":"Step 4: write the answer","level":2},{"id":"sec-5","text":"What you can do now","level":2}],"takeaways":["Start a real analysis by fixing definitions (periods, revenue, statuses) in one place, so every later step uses the same ones.","Build the query as CTE steps at explicit grains, aggregating to one row per order before joining anything that repeats.","Put comparison periods side by side with FILTER, and compute growth with NULLIF so a zero base gives NULL instead of an error.","Reconcile the final table against an independent total and list the data caveats before you write the conclusion.","Report the finding in plain sentences with the numbers that support it, including what the data cannot tell you yet."],"further":[{"title":"SQLite WITH clause","url":"https://www.sqlite.org/lang_with.html"},{"title":"SQLite aggregate functions and FILTER","url":"https://www.sqlite.org/lang_aggfunc.html"}],"quiz":[{"q":"<p>Q4 revenue grew from 52,537 to 146,825 while average order value stayed around 230 in both years. What does that tell Rania?</p>","options":[{"text":"Customers are spending much more per order.","why":"Average order value barely moved, so basket size isn’t the driver."},{"text":"Prices rose sharply between the two quarters.","why":"The March 2025 price rise was 5%, nowhere near enough to explain a near-tripling of revenue, and it would show up as a higher order value."},{"text":"Growth came from more orders, not bigger ones.","why":"Correct. Orders went from 228 to 642 at a similar value per order, so the story is volume, which points to acquisition and repeat purchases."},{"text":"The data must be wrong because growth over 100% is impossible.","why":"Revenue can more than double from a small base. Reconcile and check the data, but don’t assume large growth is an error."}],"answer":2},{"q":"<p>Jordan’s Q4 revenue grew 627%, the highest of any country. How should the review present it?</p>","options":[{"text":"Lead with it as the headline success of the quarter.","why":"Percentages on a tiny base are fragile; a few large orders can produce them."},{"text":"Report it with the base, roughly 790 to 5,760, and note that a small base makes the percentage volatile.","why":"Correct. The absolute numbers let the reader judge the size of the change, which a percentage alone hides."},{"text":"Leave Jordan out because it’s an outlier.","why":"Dropping a country changes the total and hides real growth. Context, not omission, is the fix."},{"text":"Replace the percentage with the average of all countries’ growth.","why":"That reports a number that isn’t Jordan’s, which misleads more than the original."}],"answer":1},{"q":"<p>Why does the capstone query aggregate <code dir=\"ltr\">order_items</code> into <code dir=\"ltr\">order_revenue</code> before joining it to orders and customers?</p>","options":[{"text":"Because SQLite can’t join more than three tables.","why":"SQLite handles many joins; this step is about the grain of the data, not engine limits."},{"text":"To make the query run faster.","why":"It may or may not be faster; the reason it’s there is correctness."},{"text":"To remove cancelled orders.","why":"Cancelled orders are excluded with a WHERE on <code dir=\"ltr\">orders.status</code>, in a later step."},{"text":"So every later step works at one row per order, and per-order counts and values aren’t repeated by the item lines.","why":"Correct. With items collapsed first, <code dir=\"ltr\">COUNT(*)</code> counts orders and nothing fans out, the lesson from the joins section."}],"answer":3},{"q":"<p>Sixty-three Q4 2025 orders are still ‘processing’ or ‘shipped’ on 31 December. How should they affect the report?</p>","options":[{"text":"Include them as revenue but state that some may still be cancelled or returned, so Q4 2025 could shrink slightly.","why":"Correct. They meet the agreed definition (not cancelled), and naming the open risk keeps the number honest."},{"text":"Exclude them, because revenue only counts delivered orders.","why":"That changes the definition used everywhere else in the course mid-analysis; definitions should change deliberately, not quietly."},{"text":"Ignore the issue; status doesn’t affect revenue.","why":"Status does matter, since cancellations and returns change what the business actually keeps."}],"answer":0}],"exercise":{"kind":"sql","starter":"-- Q4 2025 vs Q4 2024 revenue by channel. Write it from scratch.\n-- Definitions: Q4 = 1 Oct to 31 Dec (half-open ranges), revenue = item revenue,\n-- every status except 'cancelled'.\n-- Output: channel, revenue_q4_2024, revenue_q4_2025, growth_pct\nSELECT channel\nFROM orders\nGROUP BY channel;","solution":"WITH order_revenue AS (\n  -- grain: one row per order\n  SELECT order_id, SUM(quantity * unit_price * (1 - discount)) AS revenue\n  FROM order_items\n  GROUP BY order_id\n),\nq4_orders AS (\n  -- grain: one row per non-cancelled Q4 order\n  SELECT\n    o.channel,\n    CASE WHEN o.order_date < '2025-01-01' THEN 'Q4 2024' ELSE 'Q4 2025' END AS period,\n    r.revenue\n  FROM orders AS o\n  JOIN order_revenue AS r ON r.order_id = o.order_id\n  WHERE o.status <> 'cancelled'\n    AND (\n      (o.order_date >= '2024-10-01' AND o.order_date < '2025-01-01')\n      OR (o.order_date >= '2025-10-01' AND o.order_date < '2026-01-01')\n    )\n)\nSELECT\n  channel,\n  ROUND(SUM(revenue) FILTER (WHERE period = 'Q4 2024'), 2) AS revenue_q4_2024,\n  ROUND(SUM(revenue) FILTER (WHERE period = 'Q4 2025'), 2) AS revenue_q4_2025,\n  ROUND(100.0 * (SUM(revenue) FILTER (WHERE period = 'Q4 2025')\n               - SUM(revenue) FILTER (WHERE period = 'Q4 2024'))\n        / NULLIF(SUM(revenue) FILTER (WHERE period = 'Q4 2024'), 0), 1) AS growth_pct\nFROM q4_orders\nGROUP BY channel;","orderMatters":false,"title":"Q4 growth by channel, from a blank page","prompt":"<p>Rania’s follow-up question from the board: “Which channel drove the growth?” Write the query <strong>from scratch</strong>.</p>\n<p>Return one row per <code dir=\"ltr\">channel</code> with <code dir=\"ltr\">revenue_q4_2024</code>, <code dir=\"ltr\">revenue_q4_2025</code> (both rounded to 2 decimals) and <code dir=\"ltr\">growth_pct</code> (percentage change from Q4 2024 to Q4 2025, rounded to 1 decimal). Use the capstone’s definitions: Q4 is 1 October to 31 December, revenue is item revenue (<code dir=\"ltr\">quantity * unit_price * (1 - discount)</code>), and every status except ‘cancelled’ counts. Row order doesn’t matter.</p>\n","hints":["Plan the steps first: revenue per order, then Q4 orders of both years labelled with their period, then one row per channel.","Label each order’s period with a CASE on <code dir=\"ltr\">order_date</code>, after filtering to the two Q4 ranges (watch the parentheses around the OR).","Use <code dir=\"ltr\">SUM(revenue) FILTER (WHERE period = 'Q4 2024')</code> and the same for 2025, then <code dir=\"ltr\">100.0 * (new - old) / NULLIF(old, 0)</code> for growth."],"explanation":"<p>Collapsing items to one row per order keeps the grain clean, the half-open ranges keep every Q4 day, and FILTER puts both years in one row per channel. The answer to the board: the web more than tripled (about 27.8k to 87.5k, +214.6%) and the app grew 168%, while the marketplace grew a slower 81%. Your own channels drove the growth. A query that labels periods by joining a small <code dir=\"ltr\">periods</code> CTE, as in the lesson, is equally correct.</p>\n","checkLabels":{}},"words":688,"readMinutes":3}