[{"data":1,"prerenderedAt":157},["ShallowReactive",2],{"qa-\u002Fsql\u002Fwindow-functions\u002Fwindow-basics":3},{"page":4,"siblings":145,"blog":154},{"id":5,"title":6,"body":7,"description":48,"difficulty":52,"extension":53,"framework":54,"frameworkSlug":55,"meta":56,"navigation":57,"order":58,"path":59,"questions":60,"questionsCount":135,"related":136,"seo":137,"seoDescription":138,"stem":139,"subtopic":140,"topic":141,"topicSlug":142,"updated":143,"__hash__":144},"qa\u002Fsql\u002Fwindow-functions\u002Fwindow-basics.md","Window Basics",{"type":8,"value":9,"toc":47},"minimark",[10,15],[11,12,14],"h2",{"id":13},"about-window-function-basics","About Window Function Basics",[16,17,18,19,23,24,28,29,32,33,36,37,40,41,43,44,46],"p",{},"Window functions are SQL's analytics workhorse: they compute aggregates, rankings, and\nrow-to-row comparisons ",[20,21,22],"strong",{},"without collapsing rows",", via the ",[25,26,27],"code",{},"OVER"," clause and its\n",[25,30,31],{},"PARTITION BY","\u002F",[25,34,35],{},"ORDER BY"," parts. Interviews probe the core distinction from ",[25,38,39],{},"GROUP BY",",\nwhere windows are (and aren't) allowed, how ",[25,42,35],{}," inside ",[25,45,27],{}," turns an aggregate\ninto a running total, and the CTE pattern for filtering on a window result.",{"title":48,"searchDepth":49,"depth":49,"links":50},"",2,[51],{"id":13,"depth":49,"text":14},"medium","md","SQL","sql",{},true,1,"\u002Fsql\u002Fwindow-functions\u002Fwindow-basics",[61,66,70,74,78,82,86,90,94,98,103,107,111,115,119,123,127,131],{"id":62,"difficulty":63,"q":64,"a":65},"what-is-a-window-function","easy","What is a window function in SQL?","A **window function** performs a calculation across a set of rows — the\n**window** — that are related to the current row, **without collapsing them into\none row**. Unlike a `GROUP BY` aggregate, every input row stays in the output, but\neach gets an extra computed value.\n\n```sql\n-- each employee row keeps its detail AND gets the dept average alongside it\nSELECT name, dept_id, salary,\n       AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg\nFROM   employees;\n```\n\nThe `OVER` clause is what makes a function a window function. Window functions are\nideal for **running totals, rankings, moving averages, and row comparisons**.\n\nRule of thumb: a window function adds a per-row analytic value while keeping every\nrow — think \"aggregate without losing the detail.\"\n",{"id":67,"difficulty":63,"q":68,"a":69},"over-clause","What does the OVER clause do?","The `OVER` clause **defines the window** — the set of rows a window function\noperates on for each row. It can contain three optional parts: `PARTITION BY`\n(split rows into groups), `ORDER BY` (order rows within the window), and a **frame\nclause** (`ROWS`\u002F`RANGE`, narrowing the window further).\n\n```sql\nSELECT name, salary,\n       SUM(salary) OVER (PARTITION BY dept_id ORDER BY hire_date) AS running_total\nFROM   employees;\n```\n\nAn **empty** `OVER ()` makes the window the **entire result set** — every row sees\nall rows.\n\nRule of thumb: `OVER` turns an ordinary function into a window function and\nspecifies which rows it looks at.\n",{"id":71,"difficulty":52,"q":72,"a":73},"window-vs-group-by","What is the difference between a window function and GROUP BY?","`GROUP BY` **collapses** rows — one output row per group. A window function\n**preserves** every row and attaches the aggregate alongside each one.\n\n```sql\n-- GROUP BY: one row per department\nSELECT dept_id, AVG(salary) FROM employees GROUP BY dept_id;\n\n-- window: every employee row, plus their department's average\nSELECT name, dept_id, salary,\n       AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg\nFROM employees;\n```\n\nThis is why you can compare a row to its group (e.g. `salary` vs `dept_avg`) with\na window function — impossible with a bare `GROUP BY` because the detail rows are\ngone.\n\nRule of thumb: `GROUP BY` summarizes into fewer rows; a window function annotates\neach row without reducing the count.\n",{"id":75,"difficulty":63,"q":76,"a":77},"partition-by","What does PARTITION BY do in a window function?","`PARTITION BY` **divides** the rows into independent groups (partitions); the\nwindow function restarts for each partition. It's the windowing analog of\n`GROUP BY`, but the rows aren't collapsed.\n\n```sql\n-- numbering restarts at 1 within each department\nSELECT name, dept_id,\n       ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn\nFROM   employees;\n```\n\nWithout `PARTITION BY`, the whole result set is one single partition.\n\nRule of thumb: `PARTITION BY` says \"compute this window function separately within\neach group.\"\n",{"id":79,"difficulty":52,"q":80,"a":81},"partition-by-vs-group-by","How does PARTITION BY differ from GROUP BY?","Both split rows into groups, but the **output shape** differs:\n\n- `GROUP BY` produces **one row per group** — it reduces the result.\n- `PARTITION BY` keeps **all rows**, computing the window function within each\n  group while leaving the detail intact.\n\n```sql\n-- 1 row per dept\nSELECT dept_id, COUNT(*) FROM employees GROUP BY dept_id;\n\n-- every row, with its dept's count attached\nSELECT name, dept_id, COUNT(*) OVER (PARTITION BY dept_id) AS dept_count\nFROM employees;\n```\n\nRule of thumb: same grouping idea, different result — `GROUP BY` collapses,\n`PARTITION BY` annotates.\n",{"id":83,"difficulty":52,"q":84,"a":85},"order-by-in-over","What is the role of ORDER BY inside the OVER clause?","`ORDER BY` inside `OVER` **orders the rows within each partition**, which matters\nfor two reasons:\n\n1. **Ranking functions** (`ROW_NUMBER`, `RANK`, `LAG`, `LEAD`) need an order to be\n   meaningful.\n2. For aggregates, adding `ORDER BY` switches the default frame to a **running**\n   (cumulative) calculation up to the current row.\n\n```sql\n-- without ORDER BY: same total for whole partition\nSUM(amount) OVER (PARTITION BY user_id)\n-- with ORDER BY: a running total up to each row\nSUM(amount) OVER (PARTITION BY user_id ORDER BY order_date)\n```\n\nIt is **independent** of the query's outer `ORDER BY`, which only sorts the final\noutput.\n\nRule of thumb: `ORDER BY` in `OVER` orders rows for the window calc (and triggers\nrunning aggregates); the outer `ORDER BY` sorts the result.\n",{"id":87,"difficulty":52,"q":88,"a":89},"running-total","How do you compute a running total with a window function?","Use an aggregate (`SUM`) with `OVER (... ORDER BY ...)`. The `ORDER BY` makes the\ndefault frame cumulative — rows from the start of the partition up to the current\nrow.\n\n```sql\nSELECT order_date, amount,\n       SUM(amount) OVER (ORDER BY order_date) AS running_total\nFROM   orders;\n```\n\nAdd `PARTITION BY` to reset the running total per group (e.g. per customer):\n\n```sql\nSUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date)\n```\n\nRule of thumb: `SUM(x) OVER (ORDER BY ...)` is the canonical running total; add\n`PARTITION BY` to restart it per group.\n",{"id":91,"difficulty":63,"q":92,"a":93},"window-aggregate-functions","Can regular aggregate functions be used as window functions?","Yes — `SUM`, `AVG`, `COUNT`, `MIN`, and `MAX` all work as window functions simply\nby adding an `OVER` clause. They then compute over the window instead of\ncollapsing rows.\n\n```sql\nSELECT name, salary, dept_id,\n       COUNT(*)   OVER (PARTITION BY dept_id) AS dept_headcount,\n       MAX(salary) OVER (PARTITION BY dept_id) AS dept_max,\n       salary - AVG(salary) OVER (PARTITION BY dept_id) AS diff_from_avg\nFROM   employees;\n```\n\nThe same function name behaves as a group aggregate (with `GROUP BY`) or a window\naggregate (with `OVER`) depending on context.\n\nRule of thumb: any aggregate becomes a window function just by adding `OVER` — no\n`GROUP BY` required.\n",{"id":95,"difficulty":52,"q":96,"a":97},"ranking-vs-aggregate-windows","What are the main categories of window functions?","Window functions fall into three families:\n\n- **Aggregate windows** — `SUM`, `AVG`, `COUNT`, `MIN`, `MAX` over a window.\n- **Ranking functions** — `ROW_NUMBER`, `RANK`, `DENSE_RANK`, `NTILE`,\n  `PERCENT_RANK`, `CUME_DIST` — position a row within its partition.\n- **Value \u002F offset functions** — `LAG`, `LEAD`, `FIRST_VALUE`, `LAST_VALUE`,\n  `NTH_VALUE` — pull a value from another row in the window.\n\n```sql\nROW_NUMBER() OVER (ORDER BY score DESC)        -- ranking\nLAG(price)   OVER (ORDER BY day)               -- offset\nAVG(price)   OVER (PARTITION BY product_id)    -- aggregate\n```\n\nRule of thumb: aggregate windows summarize, ranking functions order, offset\nfunctions reach to neighboring rows.\n",{"id":99,"difficulty":100,"q":101,"a":102},"where-window-functions-allowed","hard","In which clauses can window functions be used?","Window functions are only allowed in the **`SELECT` list** and the **`ORDER BY`**\nclause. They are **not allowed** in `WHERE`, `GROUP BY`, or `HAVING`, because\nwindows are evaluated **after** those clauses (after grouping and filtering).\n\n```sql\n-- ILLEGAL: window function in WHERE\nSELECT name FROM employees\nWHERE ROW_NUMBER() OVER (ORDER BY salary) \u003C= 5;   -- error\n\n-- LEGAL: compute in a subquery\u002FCTE, then filter\nSELECT name FROM (\n    SELECT name, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn\n    FROM employees\n) t WHERE rn \u003C= 5;\n```\n\nRule of thumb: you can't filter on a window function directly — wrap it in a CTE\nor subquery and filter the alias.\n",{"id":104,"difficulty":100,"q":105,"a":106},"logical-processing-order","At what point are window functions evaluated in query processing?","Window functions run **late** in logical query processing — after `FROM`, `WHERE`,\n`GROUP BY`, and `HAVING`, but **before** the final `ORDER BY`, `DISTINCT`, and\n`LIMIT`. That ordering explains two facts:\n\n1. You can't reference a window function in `WHERE`\u002F`HAVING` (they run earlier).\n2. A window function operates on the rows that **survived** `WHERE`\u002F`GROUP BY`.\n\n```sql\n-- the SUM window only sees rows passing the WHERE filter\nSELECT name, SUM(salary) OVER ()\nFROM   employees\nWHERE  active = true;     -- filter applied BEFORE the window\n```\n\nRule of thumb: filtering happens first, windows next, final sort\u002Flimit last — so\nwindows see filtered rows but can't be filtered themselves.\n",{"id":108,"difficulty":52,"q":109,"a":110},"named-window","What is a named window (the WINDOW clause)?","The `WINDOW` clause lets you **define a window once** and reuse it by name across\nmultiple functions, avoiding repetition. It sits after `HAVING` and before\n`ORDER BY`.\n\n```sql\nSELECT name, dept_id, salary,\n       RANK()      OVER w AS rnk,\n       AVG(salary) OVER w AS dept_avg\nFROM   employees\nWINDOW w AS (PARTITION BY dept_id ORDER BY salary DESC);\n```\n\nBoth functions share window `w`. This is cleaner than repeating the same\n`PARTITION BY ... ORDER BY ...` in every function. (Supported in PostgreSQL, MySQL\n8+, SQL Server has limited support.)\n\nRule of thumb: define a window once in the `WINDOW` clause when several functions\nshare it.\n",{"id":112,"difficulty":52,"q":113,"a":114},"distinct-with-window","Can you use DISTINCT inside a window function?","Generally **no** — most databases do not support `COUNT(DISTINCT ...) OVER (...)`.\n`DISTINCT` aggregation isn't allowed with the `OVER` clause in standard SQL,\nPostgreSQL, and SQL Server.\n\n```sql\n-- typically ERRORS\nCOUNT(DISTINCT customer_id) OVER (PARTITION BY region)\n\n-- workaround: DENSE_RANK over the values, take the max\nSELECT region,\n       MAX(DENSE_RANK() OVER (PARTITION BY region ORDER BY customer_id))\n           OVER (PARTITION BY region) AS distinct_customers\nFROM orders;\n```\n\nCommon workarounds are a `DENSE_RANK` trick, or pre-aggregating distinct values in\na CTE.\n\nRule of thumb: `COUNT(DISTINCT)` as a window function usually isn't allowed —\npre-aggregate or use a `DENSE_RANK` workaround.\n",{"id":116,"difficulty":100,"q":117,"a":118},"window-function-performance","What are the performance considerations of window functions?","Window functions require the engine to **sort or hash** rows by the\n`PARTITION BY`\u002F`ORDER BY` keys, which is the main cost. Tips:\n\n- **Index** the partition\u002Forder columns so the engine can avoid a separate sort.\n- Each distinct window definition may add its own sort — **share windows** (named\n  `WINDOW` clause) where possible.\n- Filter rows **before** the window (`WHERE`) to shrink the input.\n- Beware huge partitions and unbounded frames (`RANGE` with peers) — they scan\n  more rows per output row.\n\nRule of thumb: window functions trade a sort for analytics — index the\npartition\u002Forder keys and minimize the number of distinct windows.\n",{"id":120,"difficulty":52,"q":121,"a":122},"window-vs-self-join","Why are window functions preferred over self-joins for analytics?","Before window functions, computing running totals, rankings, or row-to-row\ncomparisons required **correlated subqueries or self-joins**, which are verbose and\noften O(n²). Window functions express the same logic in **one pass**, more\nreadably and usually faster.\n\n```sql\n-- old self-join running total (slow, O(n^2))\nSELECT a.day, SUM(b.amount) AS rt\nFROM sales a JOIN sales b ON b.day \u003C= a.day\nGROUP BY a.day;\n\n-- window function (one pass)\nSELECT day, SUM(amount) OVER (ORDER BY day) AS rt FROM sales;\n```\n\nRule of thumb: replace self-joins\u002Fcorrelated subqueries for running totals and\nrankings with window functions — clearer and faster.\n",{"id":124,"difficulty":100,"q":125,"a":126},"filter-clause-window","How can you conditionally aggregate within a window?","Use the `FILTER (WHERE ...)` clause (PostgreSQL\u002FSQLite) or a `CASE` expression\ninside the aggregate (portable) to aggregate only rows meeting a condition within\nthe window.\n\n```sql\n-- PostgreSQL FILTER\nSELECT region,\n       COUNT(*) FILTER (WHERE status = 'paid') OVER (PARTITION BY region) AS paid\nFROM orders;\n\n-- portable CASE equivalent\nSELECT region,\n       SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END)\n           OVER (PARTITION BY region) AS paid\nFROM orders;\n```\n\nRule of thumb: use `FILTER (WHERE ...)` where supported, or `SUM(CASE WHEN ...)`\nfor a portable conditional window aggregate.\n",{"id":128,"difficulty":63,"q":129,"a":130},"empty-over-clause","What does an empty OVER() clause mean?","An empty `OVER ()` makes the window the **entire result set** — every row sees all\nthe rows. It's used to attach a grand total or overall aggregate to each row\nwithout grouping.\n\n```sql\nSELECT name, salary,\n       salary * 100.0 \u002F SUM(salary) OVER () AS pct_of_total_payroll\nFROM   employees;\n```\n\nHere `SUM(salary) OVER ()` is the company-wide payroll, repeated on every row, so\nyou can compute each person's share.\n\nRule of thumb: `OVER ()` with no partition or order = the whole result set —\nperfect for \"share of total\" calculations.\n",{"id":132,"difficulty":52,"q":133,"a":134},"combining-window-with-where","How do you filter rows based on a window function's result?","Since window functions aren't allowed in `WHERE`, compute the window value in a\n**CTE or subquery**, then filter the alias in the outer query. This is the standard\npattern for \"top-N per group\" and deduplication.\n\n```sql\n-- keep only the highest-paid employee per department\nWITH ranked AS (\n    SELECT name, dept_id, salary,\n           ROW_NUMBER() OVER (PARTITION BY dept_id\n                              ORDER BY salary DESC) AS rn\n    FROM employees\n)\nSELECT name, dept_id, salary\nFROM   ranked\nWHERE  rn = 1;\n```\n\nRule of thumb: wrap the window function in a CTE, then filter its output column —\nyou can't put it in `WHERE` directly.\n",18,null,{"description":48},"SQL window function interview questions — OVER, PARTITION BY, window vs GROUP BY aggregates, running totals, named windows, and where windows can be used.","sql\u002Fwindow-functions\u002Fwindow-basics","Window Function Basics","Window Functions","window-functions","2026-06-20","lU-Pdyj0JfKs5j1DsgcOY_HWoWio2OyoTkAgClSQdgI",[146,147,150],{"subtopic":140,"path":59,"order":58},{"subtopic":148,"path":149,"order":49},"Ranking Functions","\u002Fsql\u002Fwindow-functions\u002Franking-functions",{"subtopic":151,"path":152,"order":153},"Frames & Offset Functions","\u002Fsql\u002Fwindow-functions\u002Fframes-and-offsets",3,{"path":155,"title":156},"\u002Fblog\u002Fsql-window-functions-over-partition","SQL Window Functions — OVER, PARTITION BY, and When to Use Them",1782244106996]