skip to content

Window Functions vs GROUP BY

GROUP BY collapses rows to one per group while a window function keeps every row and annotates it — choosing the right one is a judgment interviewers test constantly. This covers when each applies, how they combine in one query, and where windows sit in logical evaluation order.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

5

How does SUM(sales) OVER (PARTITION BY region) differ from SUM(sales) with GROUP BY region?

level: juniorimportance: must knowfreq 72%

answer

  1. count the rows each query returns
  2. only one form keeps detail columns
  3. PARTITION BY divides rows without removing them
  4. GROUP BY collapses; the window annotates

basics

~20 s

GROUP BY collapses each region's rows into one output row, so detail columns are gone. The window version keeps every input row and attaches that row's region total to it, so the row count is unchanged.

solid answer

~40 s

Both compute the same arithmetic; they differ in the **shape of the result**. `GROUP BY region` reduces the input to one row per region — you get `region` and its total, and individual sales rows no longer exist in the output. `SUM(sales) OVER (PARTITION BY region)` is row-preserving: the query still returns one row per input row, and each row carries the total of the partition it belongs to, repeated. `PARTITION BY` divides rows into groups for the window computation only; it never removes or merges rows. So the choice is driven by what the consumer needs: a summary (one line per region) → `GROUP BY`; detail plus its group context on the same line → a window function.

code

sql · 12 lines
sql
-- sales(region, rep, amount): 3 rows in EAST (100,120,80), 2 in WEST (150,100)

SELECT region, SUM(amount) AS region_total
FROM sales
GROUP BY region;
-- 2 rows: EAST|300, WEST|250  (rep and amount are gone)

SELECT region, rep, amount,
       SUM(amount) OVER (PARTITION BY region) AS region_total
FROM sales;
-- 5 rows: EAST|ana|100|300, EAST|bo|120|300, EAST|cy|80|300,
--         WEST|di|150|250, WEST|ed|100|250

go deeper

for a junior

Be ready to state the row-count difference in one sentence and to name which form still shows the individual rows. This is the standard opening question about window functions.

for a middle

Explain that PARTITION BY slices rows for the window computation only, and show a concrete before/after result set for the same aggregate written both ways.

for a senior

Demonstrate that you choose by required output shape and downstream consumer, and that you know the window sees only post-filter rows, so a WHERE clause silently changes the totals it reports.

for a principal

Own the convention: which form the team's reporting queries use by default, and why detail-plus-context in one statement beats maintaining an aggregate subquery joined back, where a predicate must be kept in sync in two places.

## The one-line difference `GROUP BY` **collapses**. A window function **annotates**. Both can compute `SUM(sales)` per region; only the row count of the result differs. ## What GROUP BY does When a query has `GROUP BY region`, the engine partitions the rows that survived `WHERE` into groups sharing the same `region` value, and then produces **exactly one output row per group**. Everything the SELECT list asks for must be derivable from the group: the grouping column itself, or an aggregate over the group's rows. The individual rows are gone from the output — there is no place to put `rep` or `sale_id`, because a group of five rows has five different values for them and only one output row to hold them. ```sql SELECT region, SUM(amount) AS region_total FROM sales GROUP BY region; -- EAST | 300 -- WEST | 250 ``` ## What a window function does A window function is written with an `OVER` clause. It runs over the same set of rows, but it does not reduce them. For each row, the engine determines that row's **window** — here, all rows sharing its `region`, because of `PARTITION BY region` — computes the aggregate over that window, and returns the value **as an extra column on that row**. Every input row still comes out. ```sql SELECT region, rep, amount, SUM(amount) OVER (PARTITION BY region) AS region_total FROM sales; -- EAST | ana | 100 | 300 -- EAST | bo | 120 | 300 -- EAST | cy | 80 | 300 -- WEST | di | 150 | 250 -- WEST | ed | 100 | 250 ``` Notice `region_total` repeats across the rows of a partition. That repetition is the point: it lets the same row show both its own value and its group's value, so you can compute a share, a difference from the group average, or a flag like "above the regional mean" without a second query. ## PARTITION BY is not WHERE and not GROUP BY Three confusions are worth naming explicitly. - **`PARTITION BY` does not filter.** It slices the surviving rows into buckets for the window computation. Rows in other partitions are excluded from *this row's* window, not from the result set. - **`PARTITION BY` does not require a matching `GROUP BY`.** The two clauses are independent; a query can have a window function and no `GROUP BY` at all, which is the common case. - **A window function does not deduplicate.** If you want one row per region you need `GROUP BY` (or a deduplicating step around the window query, which is strictly more work for the same answer). ## Choosing between them Ask what the consumer of the result wants: - A report with one line per group, an export feeding a summary table, a set to join against at group grain → `GROUP BY`. - A detail listing where each line also shows group context — total, average, rank, share → a window function. - Both at once → they compose: a grouped query can itself carry window functions computed over the grouped rows. There is also a readability argument. Before window functions were widely available, getting detail plus group aggregate required aggregating in a subquery and joining the result back to the detail rows on the group key. That works, but it repeats the group key, duplicates any `WHERE` predicate in two places, and gives you two places to get the filter wrong. The window form expresses the same thing in one `SELECT`. ## Rules the window form still obeys A window function sees only the rows that reached it — the rows left after `WHERE` (and after grouping, in a grouped query). It does not reach back to rows the query filtered out. So `SUM(amount) OVER (PARTITION BY region)` in a query with `WHERE sale_date >= DATE '2024-01-01'` totals **this year's** sales for the region, not all of history. That is usually what you want, but it is a fact to state out loud rather than assume. ## What an interviewer is checking That you can predict the row count of each form, that you know `PARTITION BY` is a slicing device rather than a filter, and that you pick the form matching the required output shape instead of reaching for whichever one you learned first.

  • If the window form returns every row anyway, when do you still reach for GROUP BY?
    When the consumer wants group grain: a summary report, an export of one row per region, or a result you will join to at group level. Getting that from a window query means adding a deduplication step on top, which is more code for the same answer. GROUP BY states the intent directly.
  • Does PARTITION BY filter rows the way WHERE does?
    No. It only divides the rows that already survived filtering into partitions so each row's window is computed over its own partition. Every row still appears in the output. Filtering is WHERE's job, and it is applied before the window is computed.
  • Can one query use both GROUP BY and a window function?
    Yes. The window is then computed over the grouped rows, not the original detail rows. For example a query grouped by region can add a window that ranks regions by their totals or shows the grand total beside each region's total.

GROUP BY is a summary report: one printed line per region. A window function is a spreadsheet where you add a "regional total" column beside the existing rows — every original line is still on the page.

saying these in an interview costs you the question

  • Says PARTITION BY filters rows like WHERE does
  • Claims both forms return the same number of rows
  • Thinks OVER (PARTITION BY x) also requires GROUP BY x
  • Believes a window function removes duplicate rows
  • Says window functions are just a slower GROUP BY

context

open as a page

In SQL's logical evaluation order, when are window functions computed relative to WHERE, HAVING and LIMIT?

level: middleimportance: must knowfreq 62%

basics

~20 s

Window functions run after FROM, WHERE, GROUP BY and HAVING, so they see only rows that survived filtering, and before DISTINCT, ORDER BY and LIMIT/FETCH, so row limits never shrink what a window computes over.

open as a page

Why use a window function instead of joining back to a GROUP BY subquery for per-row aggregates?

level: middleimportance: should knowfreq 58%

basics

~20 s

Both put a group aggregate on every detail row, but the window version needs no subquery, no join key and no duplicated predicate: one SELECT over one row source, with the filter written once so the total always matches the rows shown.

open as a page

Is SUM(SUM(amount)) OVER () legal in a GROUP BY query, and what does it compute?

level: middleimportance: should knowfreq 44%

basics

~10 s

Yes. Window functions are evaluated after grouping, so the inner SUM aggregates each group and the outer window SUM adds those group totals up, returning the grand total repeated on every group row.

open as a page

A share-of-total column using SUM() OVER () shows shares of a filtered subset, not the company total — why?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because WHERE is applied before window functions: the window only ever sees rows that survived the filter, so its total covers the filtered subset. To divide by an unfiltered total, compute the window in an inner query over all rows and filter outside it.

open as a page