Unit 07.00: Aggregating without collapsing rows
Unit ID: SQL-M07-U01 - Estimated active time: 14-17 minutes Objective: use a window function to add an aggregate to detail rows, and say why GROUP BY cannot.
GROUP BY destroys the detail it summarises
Module 4 established that GROUP BY changes the grain: 1,000 orders become 2 status rows. That is often what you want - and sometimes exactly what you do not.
"Show each order with its status total beside it" cannot be answered by GROUP BY, because the moment you group, the individual orders are gone.
A window keeps both
SELECT order_id,
status,
order_total,
SUM(order_total) OVER (PARTITION BY status) AS status_total
FROM orders
ORDER BY order_id
LIMIT 3;
Every order row survives, and each carries the total for its status. The row count is unchanged at 1,000:
SELECT COUNT(*) FROM (
SELECT order_id, SUM(order_total) OVER (PARTITION BY status) AS t FROM orders
);
-- 1000
That preserved row count is the defining property. A window function computes across rows without collapsing them.
The share-of-total pattern
This is what windows are most often needed for:
SELECT order_id,
order_total,
ROUND(100.0 * order_total / SUM(order_total) OVER (), 4) AS pct_of_revenue
FROM orders
ORDER BY order_total DESC
LIMIT 3;
OVER () with an empty window means "all rows". Computing a percentage of the grand total normally needs two passes or a self-join; here it is one expression.
Non-example
If you do not need the detail, do not use a window:
-- Right tool
SELECT status, SUM(order_total) FROM orders GROUP BY status;
-- Wasteful: computes the total on all 1,000 rows, then discards 998 of them
SELECT DISTINCT status, SUM(order_total) OVER (PARTITION BY status) FROM orders;
Both return two rows. The first says what it means.
Practice
Write a query showing each pending order with the total value of all pending orders beside it, and confirm the row count is 12.
Check your answer
SELECT order_id,
order_total,
SUM(order_total) OVER () AS pending_total,
COUNT(*) OVER () AS pending_orders
FROM orders
WHERE status = 'pending'
ORDER BY order_total DESC;
-- 12 rows, pending_total 15558.00, pending_orders 12
WHERE runs before the window, so OVER () covers only the 12 pending rows - the filter has already narrowed the window's world.
Takeaway
GROUP BY collapses, a window does not. When you need a total *and* the rows behind it, that is the signal for OVER.
---
