OVER(), PARTITION BY and ORDER BY in Window Expressions
coreintermediateOVER() turns an aggregate-like function into a window function: it computes across a set of related rows but keeps every row in the output, instead of collapsing them the way GROUP BY does. PARTITION BY defines which rows are "related"; ORDER BY inside OVER() defines the order the window function sees them in.
Think of it as
GROUP BY answers "one row per group." A window function answers "one row per input row, but computed with awareness of its group." PARTITION BY inside OVER() is doing the same conceptual job as GROUP BY — splitting rows into groups — except the rows are never actually collapsed; each row keeps its own identity while a value is computed across its partition.
What we're doing: Show every employee row alongside their department's average salary, without collapsing any rows the way GROUP BY would.
- 3–6
- Two departments: Engineering (2 employees) and Sales (1 employee).
- 8
- avg(salary) OVER (PARTITION BY dept) computes each row's department average without collapsing the 3 rows into 2.
name | dept | salary | dept_avg
------+-------------+--------+----------
Ada | Engineering | 120000 | 115000.0
Grace | Engineering | 110000 | 115000.0
Linus | Sales | 90000 | 90000.0
(3 rows)Why this works: PARTITION BY dept groups the underlying rows exactly like GROUP BY would, but a window function computes its aggregate per partition and then attaches that value to every original row in the partition, rather than replacing the rows with one summary row per group. Ada and Grace both see 115000.0 because they share the Engineering partition, while Linus sees his own salary as the Sales partition's average since he is its only member — all three original employee rows survive.
Reaching for GROUP BY when the goal is "this row plus its group's aggregate"
Wrong
Better
What you see: A report needs to show each individual row alongside a group-level aggregate (e.g. "this employee's salary vs their department's average"), and GROUP BY makes that structurally impossible without a self-join back to the ungrouped table.
Why: GROUP BY is defined to produce exactly one output row per distinct group, which is precisely why non-aggregated, non-grouped columns like an individual employee's name cannot appear in the SELECT list at all — that information no longer has a single row to belong to. A window function was designed for exactly this shape of question, computing the aggregate without ever collapsing the underlying rows.
- GROUP BY dept
- one output row per department
- individual employee rows are gone
- SELECT dept, avg(salary) FROM employees GROUP BY dept
- avg(salary) OVER (PARTITION BY dept)
- one output row per employee, unchanged
- each row also shows its department's average
- nothing is collapsed
GROUP BY vs a window function, side by side
Together
Remember: OVER() computes across a set of related rows without collapsing them — PARTITION BY groups rows the same way GROUP BY would, but every original row survives in the output.
See also: ranking and offset functions · window frames

