Is SUM(SUM(amount)) OVER () legal in a GROUP BY query, and what does it compute?
answer
- ask what rows the window sees here
- grouping happens first
- the two SUMs run at different stages
- the outer one is a window, not an aggregate
basics
~10 sYes. 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.
solid answer
~40 sIt is legal, and it is not nested aggregation. In a grouped query the row set a window function operates on is the **grouped result** — one row per group — not the original detail rows. So `SUM(amount)` first reduces each region to a total, and `SUM(SUM(amount)) OVER ()` then sums those per-region totals across all group rows, giving the grand total beside each region. The same rule makes `RANK() OVER (ORDER BY SUM(amount) DESC)` legal: you can rank groups by their own aggregates in one query. The constraint that comes with it is the same one the SELECT list obeys — inside `OVER` you may reference grouping columns and aggregates, but not an ungrouped detail column, because that column no longer exists at this stage.
code
sql · 8 linesSELECT region,
SUM(amount) AS region_total,
SUM(SUM(amount)) OVER () AS grand_total,
RANK() OVER (ORDER BY SUM(amount) DESC) AS region_rank
FROM sales
GROUP BY region;
-- EAST | 300 | 550 | 1
-- WEST | 250 | 550 | 2go deeper
Recognise that a function with an OVER clause is a window function, not an aggregate, and that the two can appear in the same SELECT list. Deep familiarity is not expected yet.
Trace the query stage by stage out loud: group first, then window over the grouped rows. Be ready to say which expressions are legal inside OVER in a grouped query.
Show the equivalent CTE rewrite and say when you would prefer it for readability, and point out that HAVING silently narrows the window's input and therefore any grand total computed from it.
Decide the house style for two-level reporting queries — nested window-over-aggregate versus an explicit CTE stage — weighing compactness against how many people on the team can read it correctly under time pressure.
## The rule behind it Window functions are computed **after** `GROUP BY` and `HAVING`. That single fact answers the whole question. In a grouped query the input to every window function is the set of grouped rows — one row per group — so a window function in such a query is operating on summarised rows, and its arguments must be things a grouped row actually has. ## Reading the double SUM ```sql SELECT region, SUM(amount) AS region_total, SUM(SUM(amount)) OVER () AS grand_total FROM sales GROUP BY region; -- EAST | 300 | 550 -- WEST | 250 | 550 ``` Read it inside-out: 1. `GROUP BY region` produces one row per region. 2. The inner `SUM(amount)` is an ordinary aggregate: it collapses each region's detail rows to a total. After this step the intermediate rows are `(EAST, 300)` and `(WEST, 250)`. 3. `SUM(...) OVER ()` is a window function over those two rows. The empty `OVER ()` means the window is every row of that intermediate set, so it computes `300 + 250 = 550` and returns it on each row. So the two `SUM`s do different jobs at different stages. This is **not** an aggregate nested inside another aggregate, which standard SQL forbids — the outer one is a window function, and the stage boundary between grouping and windowing is exactly what makes it well defined. ## Why it is useful The pattern gives you group-level context inside a grouped report without a second query or a self-join: ```sql SELECT region, SUM(amount) AS region_total, RANK() OVER (ORDER BY SUM(amount) DESC) AS region_rank, SUM(amount) - AVG(SUM(amount)) OVER () AS vs_avg_region FROM sales GROUP BY region; ``` Each region row now knows how it ranks among regions and how far it sits from the average region — all computed over the grouped rows, all in one statement. Without windowing you would aggregate into a CTE and then either join it to itself or aggregate it a second time. ## What you may reference inside OVER The expression inside a window function — and inside its `PARTITION BY` and `ORDER BY` — must be valid at the post-grouping stage. That means: - **Grouping columns**: fine. `PARTITION BY region` in a query grouped by `region, month` is legal and gives windows over the months of a region. - **Aggregates**: fine. `ORDER BY SUM(amount) DESC`, `PARTITION BY region ORDER BY COUNT(*)`. - **Ungrouped detail columns**: not fine. In a query grouped by `region`, `ROW_NUMBER() OVER (ORDER BY rep)` is invalid, because `rep` was collapsed away — a group row has no single `rep` value to order by. Engines report this as the same class of error you get from putting `rep` bare in the SELECT list of a grouped query. ## A worked two-level example ```sql -- Share of the company total, computed per region, in one grouped query SELECT region, SUM(amount) AS region_total, 100.0 * SUM(amount) / SUM(SUM(amount)) OVER () AS pct_of_company FROM sales GROUP BY region; ``` The denominator is the sum of every group's total, so the percentages add up to 100 across the returned rows — with the important caveat that "every group" means every group **this query produced**, i.e. after `WHERE` and `HAVING`. If a `HAVING SUM(amount) > 100` dropped a small region, that region is gone before the window runs and its amount is not in the denominator. ## When to avoid the nesting Some readers find `SUM(SUM(x)) OVER ()` genuinely hard to parse, and not every engine's window support is equally complete. Both concerns have the same escape hatch: aggregate in a CTE, then apply the window to the CTE's rows in the outer query. ```sql WITH by_region AS ( SELECT region, SUM(amount) AS region_total FROM sales GROUP BY region ) SELECT region, region_total, SUM(region_total) OVER () AS grand_total FROM by_region; ``` This is exactly equivalent and spells out the two stages the nested form implies. Choose it when clarity matters more than compactness, or if your engine rejects the nesting. ## What an interviewer is checking That you know windows run after grouping — the double `SUM` is only surprising if you believe otherwise — and that you can state the restriction it implies: inside `OVER`, grouped columns and aggregates only.
- Why is SUM(SUM(amount)) legal here when nesting aggregates is normally an error?Because the outer SUM is not an aggregate in this query — it is a window function, marked by its OVER clause. Aggregates run during grouping; window functions run afterwards over the grouped rows. Two different stages, so there is no nesting. Remove the OVER clause and the same text becomes an illegal nested aggregate.
- In a query grouped by region, why is ROW_NUMBER() OVER (ORDER BY rep) invalid?Because rep no longer exists at the point the window runs. Grouping collapsed each region's rows into one, and that row has no single rep value. Only grouping columns and aggregates are visible to the window, the same rule the SELECT list follows.
- Does a HAVING clause affect the value of SUM(SUM(amount)) OVER ()?Yes. HAVING is applied to the grouped rows before window functions run, so any group it removes is not part of the window's input and its amount is absent from the grand total. That is what you want when HAVING defines the report's scope, and a trap when it does not.
saying these in an interview costs you the question
- Says nesting aggregates is always an error, without seeing the OVER clause
- Thinks the outer SUM re-reads the detail rows
- Claims the window runs before GROUP BY
- Believes an ungrouped column can be used inside OVER
- Says you need a self-join to get the grand total per group row