{"id":"l-sql-3-2","courseId":"sql-for-analysts","lang":"en","kind":"lesson","summary":"Rank rows within groups using ROW_NUMBER, RANK and DENSE_RANK with OVER (PARTITION BY … ORDER BY …), and answer top-N-per-group and first-event questions without losing detail rows.","html":"<p>Huda wants to feature the two best sellers from every category in the spring newsletter. You can get revenue per product with GROUP BY. But “the top two <em>within each category</em>” needs something GROUP BY can’t do: rank rows against their neighbours while keeping each row. That’s the job of <strong>window functions</strong>, and once you have them, a whole class of questions becomes short.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Keeping rows while looking across them</h2>\n<p><code dir=\"ltr\">GROUP BY</code> collapses each group into one row. A window function computes a value across a set of rows, the <em>window</em>, and attaches it to every row, collapsing nothing.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 190\" role=\"img\" aria-labelledby=\"l-sql-3-2-t1\"><defs><marker id=\"arrow-l-sql-3-2-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-2-t1\">Three Yoga products and one Kitchen product with their 2025 revenue. GROUP BY category returns two rows, one per category. RANK over a partition by category returns all four rows, each with its rank inside its category.</title>\n  <text class=\"d-label-strong\" x=\"120\" y=\"24\" text-anchor=\"middle\">product revenue</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"36\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"56\" text-anchor=\"middle\">Yoga   Mat      4665</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"70\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"90\" text-anchor=\"middle\">Yoga   Cushion  3923</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"104\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"124\" text-anchor=\"middle\">Yoga   Block    1656</text>\n  <rect class=\"d-box-success\" x=\"20\" y=\"146\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"166\" text-anchor=\"middle\">Kitchen Board   2039</text>\n  <text class=\"d-label-strong\" x=\"360\" y=\"24\" text-anchor=\"middle\">GROUP BY</text>\n  <rect class=\"d-box-accent\" x=\"270\" y=\"70\" width=\"180\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"360\" y=\"90\" text-anchor=\"middle\">Yoga    10244</text>\n  <rect class=\"d-box-success\" x=\"270\" y=\"146\" width=\"180\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"360\" y=\"166\" text-anchor=\"middle\">Kitchen  2039</text>\n  <text class=\"d-label-strong\" x=\"600\" y=\"24\" text-anchor=\"middle\">RANK() OVER</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"36\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"56\" text-anchor=\"middle\">Yoga   Mat      1</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"70\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"90\" text-anchor=\"middle\">Yoga   Cushion  2</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"104\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"124\" text-anchor=\"middle\">Yoga   Block    3</text>\n  <rect class=\"d-box-success\" x=\"490\" y=\"146\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"166\" text-anchor=\"middle\">Kitchen Board   1</text>\n</svg><figcaption>GROUP BY collapses a group; a window function annotates every row</figcaption></figure>\n<p>The syntax is a function followed by <code dir=\"ltr\">OVER (...)</code>. Inside the parentheses, <code dir=\"ltr\">PARTITION BY</code> splits rows into groups, and the calculation restarts in each group; <code dir=\"ltr\">ORDER BY</code> sets the order the function walks through. Leave out PARTITION BY and the whole result is one window.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ROW_NUMBER, RANK and DENSE_RANK</h2>\n<p>The three ranking functions differ only in how they treat ties. Here are the most frequent UAE buyers of 2025:</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\"> uae_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:#CF222E;--shiki-dark:#FF7B72\">  SELECT</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 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 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\"> 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\">  WHERE</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:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'United Arab Emirates'</span></span>\n<span class=\"line\"><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\">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:#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:#0A3069;--shiki-dark:#A5D6FF\"> '2025-01-01'</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:#0A3069;--shiki-dark:#A5D6FF\"> '2026-01-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</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:#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\">  customer_id,</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\">  ROW_NUMBER</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, customer_id) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> row_num,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  RANK</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> rnk,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  DENSE_RANK</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> dense_rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> uae_orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 8</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>Customers 163 and 534 both placed 11 orders. <code dir=\"ltr\">ROW_NUMBER</code> still gives them 4 and 5, unique positions, using <code dir=\"ltr\">customer_id</code> to break the tie. <code dir=\"ltr\">RANK</code> gives both 4 and then jumps to 6, like a sports table. <code dir=\"ltr\">DENSE_RANK</code> gives both 4 and continues with 5.</p>\n<p>Pick by the question. “Exactly one row per group” (the latest order, the first visit) is ROW_NUMBER. “Top 3, and include anyone tied for third” is RANK. “The three highest <em>values</em>” is DENSE_RANK.</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>Non-deterministic ROW_NUMBER</span></p>\n<p><code dir=\"ltr\">ROW_NUMBER() OVER (ORDER BY orders DESC)</code> without a tie-breaker numbers tied rows in whatever order the engine meets them, and that can change between runs. A report that says “customer 534 is fourth” on Monday and “fifth” on Tuesday loses trust quickly. End every ROW_NUMBER ordering with a unique column.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Top N per group</h2>\n<p>Window functions run after WHERE, in the SELECT step, so you can’t filter on a rank in the same query’s WHERE. The standard pattern is a CTE that computes the rank and an outer query that filters it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-index=\"1\"><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\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked </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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    category,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    product,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    revenue,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    RANK</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\">PARTITION</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> revenue </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> product_revenue</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:#1F2328;--shiki-dark:#E6EDF3\"> category, product, revenue, rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rnk </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p><code dir=\"ltr\">product_revenue</code> here stands for the per-product revenue CTE you’d write first, using the join from the joins lesson. Building it is the exercise.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">First events: one row per customer</h2>\n<p>The same tool answers a question Huda has wanted for months: which channel brings in <em>new</em> customers? That’s the channel of each customer’s first order, which is one row per partition:</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:#1F2328;--shiki-dark:#E6EDF3\"> numbered </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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    customer_id,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    channel,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    ROW_NUMBER</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>\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\"> customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date, order_id</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\"> order_seq</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:#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\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> first_order_channel, </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\"> customers</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> numbered</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_seq </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> channel</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers </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=\"2\" hidden></div></div>\n<p>Web acquires 268 customers, the app 172 and the marketplace 63. Compare that with total orders by channel and you’ll see whether the marketplace mostly finds new buyers or serves existing ones. <code dir=\"ltr\">order_seq</code> is also useful as a column in its own right: <code dir=\"ltr\">order_seq = 2</code> finds second orders, the start of every repeat-purchase analysis.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Buckets instead of positions</h2>\n<p>Sometimes the question isn’t “who is first?” but “which tier is each customer in?” Karim’s finance team splits customers into quartiles by lifetime revenue to compare discount usage across tiers. <code dir=\"ltr\">NTILE(n)</code> deals the ordered rows into n buckets of near-equal size:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"3\"><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=\"3\" 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\"> lifetime</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\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">quantity</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">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:#0550AE;--shiki-dark:#79C0FF\"> oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">discount</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</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\"> order_items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> oi </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> oi</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:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</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:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">tiered </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\"> customer_id, revenue,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         NTILE</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 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:#1F2328;--shiki-dark:#E6EDF3\"> revenue </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> quartile</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> lifetime</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:#1F2328;--shiki-dark:#E6EDF3\"> quartile,</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\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers,</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\">MIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue), </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\"> min_revenue,</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\">SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue), </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</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> tiered</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> quartile</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> quartile;</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>The top quartile’s revenue dwarfs the bottom one, the familiar shape of almost every customer base. Notice the layering: aggregate to one row per customer, assign a tier with a window function, then group again by tier. Windows and GROUP BY aren’t rivals; most real analyses use both, in separate steps.</p>\n<p><code dir=\"ltr\">NTILE</code> splits by row count, not by value, so customers with identical revenue can land in different buckets. For tiers with fixed thresholds (“over 2,000 is gold”), a CASE expression is the honest tool.</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>Name the window once</span></p>\n<p>When several functions share a window, SQLite and PostgreSQL let you define it once: <code dir=\"ltr\">... OVER w FROM orders WINDOW w AS (PARTITION BY customer_id ORDER BY order_date, order_id)</code>. It keeps long SELECT lists readable and guarantees the functions agree.</p>\n</aside>\n<p>Ranking is one half of window functions. The other half, running totals and comparisons with the previous row, turns a monthly table into a trend, and it’s next.</p>\n","toc":[{"id":"sec-1","text":"Keeping rows while looking across them","level":2},{"id":"sec-2","text":"ROW_NUMBER, RANK and DENSE_RANK","level":2},{"id":"sec-3","text":"Top N per group","level":2},{"id":"sec-4","text":"First events: one row per customer","level":2},{"id":"sec-5","text":"Buckets instead of positions","level":2}],"takeaways":["A window function computes a value from a set of related rows but keeps every row, unlike GROUP BY, which collapses them.","PARTITION BY restarts the calculation for each group; ORDER BY inside OVER defines the sequence the ranking follows.","ROW_NUMBER gives unique positions, RANK leaves gaps after ties (1, 1, 3) and DENSE_RANK doesn’t (1, 1, 2).","Window functions are computed after WHERE, so to filter on a rank, compute it in a CTE and filter in the outer query.","Make ROW_NUMBER deterministic by adding a unique tie-breaker to its ORDER BY."],"further":[{"title":"SQLite window functions","url":"https://www.sqlite.org/windowfunctions.html"},{"title":"PostgreSQL window functions tutorial","url":"https://www.postgresql.org/docs/current/tutorial-window.html"}],"quiz":[{"q":"<p>Three products have revenue 900, 900 and 700. What do RANK and DENSE_RANK (ordered by revenue descending) give the 700 product?</p>","options":[{"text":"RANK 3, DENSE_RANK 2","why":"Correct. RANK skips position 2 because two rows share position 1; DENSE_RANK continues with the next integer."},{"text":"RANK 2, DENSE_RANK 3","why":"That’s reversed. DENSE_RANK is the one without gaps, so it’s never larger than RANK."},{"text":"RANK 2, DENSE_RANK 2","why":"That would be true only without the tie at the top. RANK is one plus the number of rows ranked above, here 1 + 2 = 3."},{"text":"RANK 3, DENSE_RANK 3","why":"DENSE_RANK doesn’t leave gaps, so 700, the second distinct value, gets 2, not 3."}],"answer":0},{"q":"<p>Why does <code dir=\"ltr\">SELECT ... WHERE ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) = 1</code> fail?</p>","options":[{"text":"ROW_NUMBER needs an argument.","why":"ROW_NUMBER takes no arguments; the empty parentheses are correct."},{"text":"PARTITION BY can’t be used with ORDER BY.","why":"They’re designed to be combined; the partition restarts the numbering and the order sets it."},{"text":"Window functions only work on numeric columns.","why":"ROW_NUMBER doesn’t read any column’s values; it numbers rows in the given order."},{"text":"Window functions are computed after WHERE, so they can’t be used in it.","why":"Correct. Compute the row number in a CTE or subquery, then filter <code dir=\"ltr\">WHERE rn = 1</code> in the outer query."}],"answer":3},{"q":"<p>You number each customer’s orders with <code dir=\"ltr\">ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY date(order_date))</code>. Two orders on the same day get 1 and 2 in an unpredictable order. What’s the fix?</p>","options":[{"text":"Use RANK instead, so both get 1.","why":"Then the customer has two “first” orders, and a filter on rank 1 returns both, duplicating the customer."},{"text":"Remove PARTITION BY.","why":"Without the partition, the numbering runs across all customers, and only one order in the whole table gets 1."},{"text":"Add a unique tie-breaker, such as <code dir=\"ltr\">order_id</code>, after the date in the window’s ORDER BY.","why":"Correct. With a unique final key the ordering is total, so the same row gets 1 every time."}],"answer":2},{"q":"<p>How many rows does <code dir=\"ltr\">SELECT customer_id, COUNT(*) OVER (PARTITION BY customer_id) FROM orders</code> return?</p>","options":[{"text":"One per order, each showing how many orders that customer has.","why":"Correct. The window counts rows in each customer’s partition but doesn’t collapse them, so every order row survives."},{"text":"One per customer.","why":"That’s what <code dir=\"ltr\">GROUP BY customer_id</code> would return. A window function never changes the row count."},{"text":"One row with the total number of orders.","why":"That’s a plain <code dir=\"ltr\">COUNT(*)</code> without GROUP BY or OVER."}],"answer":0}],"exercise":{"kind":"sql","starter":"-- Top 2 products by 2025 revenue in each category\nWITH product_revenue AS (\n  SELECT\n    c.name AS category,\n    p.name AS product,\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  JOIN products AS p ON p.product_id = oi.product_id\n  JOIN categories AS c ON c.category_id = p.category_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 c.name, p.name\n),\nranked AS (\n  SELECT\n    category,\n    product,\n    revenue,\n    ROW_NUMBER() OVER (ORDER BY revenue DESC) AS rnk\n  FROM product_revenue\n)\nSELECT category, product, ROUND(revenue, 2) AS revenue, rnk\nFROM ranked\nWHERE rnk <= 2;","solution":"WITH product_revenue AS (\n  SELECT\n    c.name AS category,\n    p.name AS product,\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  JOIN products AS p ON p.product_id = oi.product_id\n  JOIN categories AS c ON c.category_id = p.category_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 c.name, p.name\n),\nranked AS (\n  SELECT\n    category,\n    product,\n    revenue,\n    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) AS rnk\n  FROM product_revenue\n)\nSELECT category, product, ROUND(revenue, 2) AS revenue, rnk\nFROM ranked\nWHERE rnk <= 2;","orderMatters":false,"title":"Two best sellers per category","prompt":"<p>Finish Huda’s newsletter list: the <strong>top 2 products by 2025 revenue within each category</strong> (non-cancelled orders). If two products tie for second place, keep both.</p>\n<p>Return <code dir=\"ltr\">category</code>, <code dir=\"ltr\">product</code>, <code dir=\"ltr\">revenue</code> (rounded to 2 decimals) and <code dir=\"ltr\">rnk</code> (the product’s rank inside its category, 1 = best). Row order doesn’t matter.</p>\n<p>The starter’s <code dir=\"ltr\">product_revenue</code> CTE is correct. Its ranking isn’t: it returns only two products for the whole store.</p>\n","hints":["Where does the ranking restart in the starter? Which part of <code dir=\"ltr\">OVER (...)</code> makes it restart for each category?","Add <code dir=\"ltr\">PARTITION BY category</code> before the <code dir=\"ltr\">ORDER BY</code> inside OVER.","Because ties should both be kept, use <code dir=\"ltr\">RANK()</code> rather than <code dir=\"ltr\">ROW_NUMBER()</code>."],"explanation":"<p>Without PARTITION BY, the whole result is one window, so ROW_NUMBER numbers all products together and <code dir=\"ltr\">rnk &lt;= 2</code> keeps the store’s top two. <code dir=\"ltr\">PARTITION BY category</code> restarts the ranking in every category, and RANK gives tied products the same number so a tie for second keeps both. Kitchen and Outdoor each contain a single product, so they return one row each, and that’s the misfiled-products issue from the joins lesson showing up again.</p>\n","checkLabels":{}},"words":678,"readMinutes":3}