PARTITION BY and ORDER BY
PARTITION BY splits rows into independent windows; ORDER BY inside OVER orders rows within each one. The twist: adding ORDER BY silently changes the default frame from the whole partition to a running range.
A revenue report ships. Finance flags it the next morning: the “region total” column is wrong — it’s showing a running total that grows down the page instead of the same flat total per region. The query had SUM(amount) OVER (PARTITION BY region ORDER BY sale_date). Nobody asked for a running total. But adding ORDER BY inside OVER silently turned the aggregate into one. This lesson is about the two clauses that shape a window, and the trap hiding in the second one.
PARTITION BY: independent windows side by side
PARTITION BY is the spatial dimension of a window. It chops the result set into groups and computes the window function independently inside each group, resetting at every boundary. Think of each partition as its own private window that knows nothing about the others.
SELECT region, salesperson, amount,
SUM(amount) OVER (PARTITION BY region) AS region_total,
AVG(amount) OVER (PARTITION BY region) AS region_avg
FROM sales;Every West row sees only West rows; every East row sees only East. The region_total for a West sale is the sum of all West amounts and nothing else. This is exactly the role GROUP BY region plays in a grouped query — but here the rows are never collapsed. Omit PARTITION BY and the whole result set is one giant partition (a grand total over all rows).
ORDER BY inside OVER: order within the partition
ORDER BY inside the OVER (...) clause is the temporal dimension — it defines the order of rows within each partition. This is completely separate from the query’s outer ORDER BY, which only sorts the final output. The window’s ORDER BY is what makes “running” and “row-relative” computations possible: it gives the rows a sequence, so a function can say “everything up to here,” “the previous row,” “the rank so far.”
SELECT region, sale_date, amount,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_date) AS nth_sale,
SUM(amount) OVER (PARTITION BY region ORDER BY sale_date) AS running_total
FROM sales;ROW_NUMBER() needs the order to number 1, 2, 3 within each region by date. SUM with that same window now produces a running total — and that is the surprise.
The default-frame trap
Here is the rule that bites people. When OVER has no ORDER BY, the window’s frame — the slice of rows the function actually aggregates — defaults to the entire partition. So SUM(amount) OVER (PARTITION BY region) sums every row in the region: one flat number, repeated.
The instant you add ORDER BY inside OVER, the default frame silently changes to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — “from the first row of the partition up to the current row.” That is a running aggregate, not a partition-wide one. Same SUM, same PARTITION BY, but adding an inner ORDER BY flips a flat total into a cumulative one.
-- Flat region total (no inner ORDER BY): same value on every West row
SUM(amount) OVER (PARTITION BY region)
-- Running total (inner ORDER BY): grows down the partition
SUM(amount) OVER (PARTITION BY region ORDER BY sale_date)That is the bug from the Hook. The author wanted a flat region total but added ORDER BY sale_date (perhaps copied from a neighboring ROW_NUMBER), and the frame quietly became cumulative. The fix is to remove the inner ORDER BY for a flat total, or — if some columns need ordering and others don’t — name the windows explicitly so each gets the frame it wants.
▸Common mistake
The deepest part of this trap is that it is silent — no error, no warning, just a number that’s subtly wrong and passes code review because the SQL looks reasonable. Two senior habits defend against it. First: if you want a flat aggregate, never put ORDER BY in that window. Second: when a query mixes flat and running aggregates, define named windows with WINDOW w_flat AS (PARTITION BY region) and WINDOW w_run AS (PARTITION BY region ORDER BY sale_date), then reference OVER w_flat / OVER w_run. The next lesson, on frames, makes the running-vs-flat behavior fully explicit.
What does PARTITION BY region do inside an OVER clause?
You want a FLAT region total on every row, but you wrote SUM(amount) OVER (PARTITION BY region ORDER BY sale_date). What goes wrong?
Fill in the blank: with no inner ORDER BY, the window's frame is the whole partition (a flat total). Adding ORDER BY inside OVER silently changes the default frame to a _______ range, turning a flat aggregate into a cumulative one.
- 01What is the difference between PARTITION BY and ORDER BY inside an OVER clause?
- 02Why does SUM(amount) OVER (PARTITION BY region ORDER BY sale_date) give a running total instead of a flat region total?
- 03You need both a flat region total and a running total in the same query. How do you avoid the frame trap?
A window is shaped by two clauses inside OVER (...). PARTITION BY is spatial: it carves the rows into independent windows that reset at each boundary, so a function only ever sees rows in its own partition — the same scoping GROUP BY does, minus the collapse. ORDER BY inside OVER is temporal: it orders rows within each partition so functions can compute “up to here,” “previous row,” or “rank so far.” The trap is that this inner ORDER BY is not just a sort — it silently changes the default frame from the entire partition to RANGE UNBOUNDED PRECEDING .. CURRENT ROW, flipping a flat aggregate into a running one with no error. The defense is intentional windows: omit the inner ORDER BY for flat totals, add it only for running ones, and name your windows when a query needs both. Now when you see a window query return a running total where you expected a flat one, check the inner ORDER BY first — nine times out of ten, that is the culprit. The next lesson makes frames fully explicit — ROWS vs RANGE vs GROUPS, and the bound syntax — so you control exactly which rows each function aggregates.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.