Phase 5 · Window functions: ranking, running totals and comparing rows · Topic 5.1

What a window function is: OVER and PARTITION BY

Must know

A window function calculates a value for each row from a set of related rows (its "window"), defined by OVER (PARTITION BY … ORDER BY …), and — unlike GROUP BY — keeps every row in the result.

Not started

Step 1 Understand it

Why you need it. "Show each employee with their department's average salary next to them." gives you the averages but throws away the employees; joining the averages back is clumsy. A puts the group's number on every in one step.

New words in this topic (tap one to see what it means)

Watch it happen

AnimationA window function adds a group's answer to every row, without removing any
Read the animation as text
  1. Seven employees with a department. We want each one next to their department's average salary.
  2. PARTITION BY dept_id splits the rows into windows, one per department — like GROUP BY's piles. But nothing is squashed.
  3. AVG(salary) is worked out over each window, and every row gets its own window's answer. All 7 rows stay.
  4. Compare GROUP BY: one row per department, and the individual names are gone. A window function keeps the detail AND adds the group's number.

Picture it

flowchart LR
  subgraph W1["window: dept 1 · avg 78750"]
    A["Asha 95000"]
    B["Ben 70000"]
    C["Chen 70000"]
    H["Hana 80000"]
  end
  subgraph W2["window: dept 2 · avg 52500"]
    D["Dara 60000"]
    E["Eli 45000"]
  end
  W1 --> R["every row kept,<br/>each with its own window's average"]
  W2 --> R
draws a box around related rows. The function is worked out inside each box, and the answer is written onto every row in it — no row disappears.

The idea

Add (…) after a function and it becomes a window function. OVER () with nothing inside means "the window is every row". PARTITION BY dept_id splits the rows into windows, one per department, like GROUP BY — but the rows stay separate, and each row gets its own window's answer. inside OVER sets an order within each window, which ranking and need. Any (, AVG, , MIN, MAX) can be used this way, plus special window-only functions such as and .

The shape of it

sql
function(...) OVER (
  PARTITION BY group_column
  ORDER BY sort_column
)

Words in lower case are the parts you fill in.

How it works, step by step

  1. and produce the rows: 7 employees with a department.
  2. PARTITION BY dept_id makes three windows: department 1 (4 people), 2 (2 people), 3 (1 person).
  3. AVG(salary) is worked out over each window: 78750, 52500, 52000.
  4. Each row gets the answer for its own window, and no rows are removed.
  5. OVER () with no partition makes one window of all rows: COUNT(*) OVER () is 8 on every row.

Analogy

A class photo where each pupil holds a card. GROUP BY would replace each form group with one person holding the group's average. A window function keeps every pupil in the photo, and each one's card shows their own form group's average.

Where the analogy breaks

In the photo the card is written once. A window function is worked out in the step, after WHERE and GROUP BY — so WHERE can't filter on it. To filter on a window result, wrap the in a .

Worked examples

Each employee next to their department average

sql
SELECT name, dept_id, salary,
       AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg
FROM employees
WHERE dept_id IS NOT NULL
ORDER BY dept_id, name;

7 rows

namedept_idsalarydept_avg
Asha19500078750
Ben17000078750
Chen17000078750
Hana18000078750
Dara26000052500
Eli24500052500
Fatima35200052000

GROUP BY dept_id would give 3 rows. The window keeps all 7.

Group sizes and the grand total on every row

sql
SELECT name, dept_id,
       COUNT(*) OVER (PARTITION BY dept_id) AS dept_size,
       COUNT(*) OVER () AS company_size
FROM employees
ORDER BY emp_id;

8 rows

namedept_iddept_sizecompany_size
Asha148
Ben148
Chen148
Dara228
Eli228
Fatima318
GusNULL18
Hana148

OVER () is one window holding every row. Gus forms his own partition.

How far each salary is from the department average

sql
SELECT name, salary,
       salary - AVG(salary) OVER (PARTITION BY dept_id) AS diff_from_avg
FROM employees
WHERE dept_id = 1
ORDER BY salary DESC, name;

4 rows

namesalarydiff_from_avg
Asha9500016250
Hana800001250
Ben70000-8750
Chen70000-8750

Must-know points

  • A window function keeps every row; GROUP BY collapses rows.
  • PARTITION BY = which rows share a window. No PARTITION BY = one window of all rows.
  • ORDER BY inside OVER = the order within each window (needed for ranks, LAG and running totals).
  • Window functions run in the SELECT step: you can't use them in WHERE, GROUP BY or . Wrap in a CTE to filter.
  • Any aggregate becomes a window function when you add OVER.

Common mistakes

  • Writing WHERE ROW_NUMBER() OVER (…) = 1 — not allowed. Filter in an outer query.
  • Adding GROUP BY when you wanted every row kept.
  • Confusing the ORDER BY inside OVER (order within the window) with the query's ORDER BY (order of the output).

In other databases

  • Window functions work in , , Oracle, 8+ and 3.25+. Older MySQL (5.7) has none.

Step 2 Go further (free, optional)

Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.

Step 3 Practise

Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.

  1. ExplainSay what it does and when to use it, in plain words, with no notes.

    Explain the difference between AVG(salary) … GROUP BY dept_id and AVG(salary) OVER (PARTITION BY dept_id).

    Show answer

    AVG(salary) … GROUP BY dept_id squashes each department into one row: 8 employees in, 4 rows out (one per department, Gus's NULL included). The names are gone.

    AVG(salary) OVER (PARTITION BY dept_id) works out the same averages but keeps all 8 rows. Each employee gets their department's average written next to them, so you can compare: "Ben earns 70,000; his department averages 78,750".

    Use GROUP BY when you want one row per group, and a window function when you want every row plus a group figure.

    sql
    SELECT dept_id, AVG(salary) AS dept_avg
    FROM employees
    GROUP BY dept_id
    ORDER BY dept_id;

    4 rows

    dept_iddept_avg
    NULL38000
    178750
    252500
    352000
  2. Writefrom a blank page, then run it

    For every order, show the order_id, amount, and the customer's total spend across all their orders.

    Show answer

    PARTITION BY customer_id makes one window per customer, and SUM adds up that customer's amounts. Every order keeps its own row. Order 106's NULL is skipped, so Lena's total is 60.

    sql
    SELECT order_id, customer_id, amount,
           SUM(amount) OVER (PARTITION BY customer_id) AS customer_total
    FROM orders
    ORDER BY order_id;

    10 rows

    order_idcustomer_idamountcustomer_total
    1011120425
    102180425
    103145425
    1042200275
    1051150425
    1063NULL60
    10736060
    108275275
    109130425
    11053333
  3. Predictsay it before you run it

    What does SELECT name, MAX(salary) OVER () FROM employees; return? How many rows?

    Show answer

    8 rows, one per employee, each with 95,000 next to the name.

    OVER () is an empty window: no PARTITION BY, so the window is the whole , and the highest salary in the whole table is Asha's 95,000. A window function never removes rows. (The query has no ORDER BY, so add one if you want the names in a fixed order.)

    sql
    SELECT name, MAX(salary) OVER ()
    FROM employees
    ORDER BY emp_id;

    8 rows

    nameMAX(salary) OVER ()
    Asha95000
    Ben95000
    Chen95000
    Dara95000
    Eli95000
    Fatima95000
    Gus95000
    Hana95000
  4. Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.

    Gus has a NULL dept_id. What dept_avg does he get in the first example if you remove the WHERE?

    Show answer

    38,000: his own salary.

    PARTITION BY, like GROUP BY, puts all NULLs into one group. Gus is the only employee with a NULL dept_id, so he is alone in his window and the average of one salary is that salary.

    That is technically right but can mislead: "no department" is not a real department. That is why the example filtered him out with WHERE dept_id IS NOT NULL.

    sql
    SELECT name, dept_id, salary,
           AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg
    FROM employees
    ORDER BY dept_id, name;

    8 rows

    namedept_idsalarydept_avg
    GusNULL3800038000
    Asha19500078750
    Ben17000078750
    Chen17000078750
    Hana18000078750
    Dara26000052500
    Eli24500052500
    Fatima35200052000

Practice box: write and run your answers here

Ctrl/⌘ + Enter runs it. Select part of the query to run only that part.

Step 4 Interview-ready, then prove it

Your interview answer

About 20 to 40 seconds, said out loud.

A window function computes a value over a set of rows related to the current row, defined by the OVER clause — PARTITION BY says which rows share the window and ORDER BY orders them — but unlike GROUP BY it doesn't collapse rows, so each row keeps its detail and gains the computed value. Because they are evaluated in the SELECT phase, I filter on them from an outer query or CTE.

Follow-up questions

GROUP BY or a window function — how do you choose?

If you want one row per group, GROUP BY. If you want every row plus a group value next to it (an average, a rank, a running total), a window function.

Why can't you use a window function in WHERE?

WHERE runs before the SELECT step where window functions are computed. Put the window function in a CTE and filter outside.

Tick each check when you can do it without notes

A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.

  • Explain Say what it does and when to use it, in plain words, with no notes.
  • Write Write the query from a blank page, then run it to check.
  • Predict Read a query and say which rows it returns before you run it.
  • Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.

0 of 4 checks ticked.

Saved in this browser. Back it up or move it to another device.