Skip to content

Intermediate: combining and changing data · Window functions · topic 32 of 62 · 35 min

OVER and PARTITION BY

Explanation

A window function works out a value from a group of related rows, called a window, but keeps every row in the result. GROUP BY squeezes each group into one row; a window function such as AVG(salary) OVER (PARTITION BY dept_id) puts the department average next to every employee. OVER () with empty brackets means "all rows".

The problem GROUP BY cannot solve

In module 4 you used GROUP BY to get one row per department with its average salary. But a manager often wants something else: every employee, with their own salary and the department average next to it, so she can see who is above or below. GROUP BY cannot do that, because it collapses each group into a single row and the individual names are gone.

sql
SELECT dept_id, AVG(salary) FROM employees GROUP BY dept_id;
-- NULL | 45000
-- 1    | 140000
-- 2    | 80666.67   (rounded here)
-- 3    | 86500
-- 4    | 80000
GROUP BY: 11 employees become 5 rows. The names are lost.

A window function keeps the rows

A window function works out a value over a set of rows related to the current row, called the window, and writes the answer on the current row. No row disappears. You turn an ordinary aggregate such as SUM, AVG, COUNT, MIN or MAX into a window function by adding the word OVER after it.

sql
SELECT name, dept_id, salary,
       AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg
FROM employees;
-- Meera Iyer   | 1 | 250000 | 140000
-- Rahul Sharma | 1 | 120000 | 140000
-- Anita Desai  | 1 | 95000  | 140000
-- Vikram Singh | 1 | 95000  | 140000
-- Pooja Gupta  | 2 | 110000 | 80666.666...
-- ... 11 rows in all
Every employee stays, and each row carries the average of its own department.
Think of it like this: Think of a class photo where the teacher writes the class average on a small card in front of every student. GROUP BY is like replacing the whole photo with just the card. A window function keeps the photo and adds the card to each person.

OVER () and PARTITION BY

  • OVER () with empty brackets: the window is every row of the result. SUM(salary) OVER () puts the total payroll, 1100000, on all 11 rows.
  • OVER (PARTITION BY dept_id): the rows are split into partitions, one per department, and each row sees only its own partition. A partition is like a group in GROUP BY, except the rows are not squeezed together.
  • You can partition by several columns: PARTITION BY city, course.
  • NULL forms its own partition, just as it forms its own group in GROUP BY. Deepak (no department) is alone in his partition, so his "department average" is his own salary.

Comparing a row with its group

Because the group value sits on the same row, you can do arithmetic with it. Two everyday questions are "how far is each person from the department average?" and "what share of the department payroll does each person take?".

sql
SELECT name, dept_id, salary,
       salary - AVG(salary) OVER (PARTITION BY dept_id) AS vs_avg,
       ROUND(100.0 * salary / SUM(salary) OVER (PARTITION BY dept_id), 1) AS pct_of_dept
FROM employees
WHERE dept_id = 1;
-- Meera Iyer   | 1 | 250000 | 110000 | 44.6
-- Rahul Sharma | 1 | 120000 | -20000 | 21.4
-- Anita Desai  | 1 | 95000  | -45000 | 17
-- Vikram Singh | 1 | 95000  | -45000 | 17
Multiply by 100.0 (not 100) so the division keeps its decimals in SQLite.

When is the window worked out?

Window functions run late: after FROM, WHERE, GROUP BY and HAVING, at the same time as the SELECT list, and before ORDER BY and LIMIT. Two things follow. First, a WHERE changes what the window sees: in the query above, the percentages are shares of Engineering only, because the other rows were already filtered out. Second, you cannot use a window function inside WHERE; topic 5 shows the way around that.

You can even put a window over a GROUP BY result. SUM(SUM(qty * price)) OVER () means "the total of the group totals", which gives each category's share of all revenue in one query.

sql
SELECT p.category, SUM(i.qty * i.price) AS revenue,
       ROUND(100.0 * SUM(i.qty * i.price) / SUM(SUM(i.qty * i.price)) OVER (), 1) AS pct
FROM order_items i JOIN products p ON p.id = i.product_id
GROUP BY p.category
ORDER BY revenue DESC;
-- Fashion     | 7695 | 47.1
-- Electronics | 3998 | 24.5
-- Grocery     | 2750 | 16.8
-- Kitchen     | 1900 | 11.6
The inner SUM is the GROUP BY total; the outer SUM ... OVER () adds up those totals.

Where it works

Window functions are part of standard SQL. They work in PostgreSQL, SQL Server, Oracle, SQLite 3.25 and later, MySQL 8.0 and later, and MariaDB 10.2 and later. Very old MySQL 5.7 servers do not have them.

Common mistakes

  • Expecting fewer rows. A window function never removes rows; if you want one row per group, use GROUP BY.
  • Writing 100 * salary / SUM(...) and getting whole numbers. Use 100.0 so the division keeps decimals.
  • Forgetting that WHERE runs first. If you filter to one department, OVER () totals only that department.
  • Mixing a plain column and an aggregate without OVER: SELECT name, AVG(salary) FROM employees returns one row in SQLite and an error in most other databases. Add OVER () or a GROUP BY.
  • Putting a window function in WHERE. It gives "misuse of window function". Compute it in a CTE first (topic 5).

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

OVER and PARTITION BY · 1/7

Window functions

Welcome to window functions, one of the most useful tools in SQL for reports and interviews.

Prefer a video by a teacher? Find free videos on “sql window functions over partition by explained” on YouTube ↗

In real life: A salary benchmarking report in HR

An HR team preparing appraisals usually wants a sheet with one line per employee: salary, the average and the highest salary in the same department, and the gap. A window query produces that sheet straight from the employees table, so nobody has to copy averages by hand from one report into another, and the numbers update when salaries change.

sql
SELECT name, dept_id, salary,
       ROUND(AVG(salary) OVER (PARTITION BY dept_id), 2) AS dept_avg,
       MAX(salary) OVER (PARTITION BY dept_id)           AS dept_max,
       MAX(salary) OVER (PARTITION BY dept_id) - salary  AS gap_to_top
FROM employees
ORDER BY dept_id, salary DESC;
Kavya Menon (Sales, 72000): dept_avg 80666.67, dept_max 110000, gap_to_top 38000.

Practice (0/4 done)

Task 1 · Code · SQL

Put the department average next to every employee. From employees (COMPANY), return name, dept_id, salary and the average salary of the employee's department rounded to 2 decimal places, named dept_avg. Return all 11 employees.

Task 2 · Code · SQL

What share of the department payroll does each Engineering employee (dept_id = 1) take? Return name, salary and 100.0 * salary divided by the department's total salary, rounded to 1 decimal place, named pct_of_dept.

Task 3 · Code · SQL

From students, return name, city and the number of students in the same city, named city_count, for every student.

Task 4 · Code · SQL

Return name, salary, the total payroll of the whole company named payroll, and each salary as a percentage of that total rounded to 1 decimal place, named pct_of_total. Return all employees.

Quick quiz

Question 1

1.employees has 11 rows. How many rows does SELECT name, AVG(salary) OVER (PARTITION BY dept_id) FROM employees return?

Question 2

2.What does SUM(salary) OVER () put on each row?

Question 3

3.A query has WHERE dept_id = 2 and SUM(salary) OVER (). What does the sum cover?

Question 4

4.What is the main difference between GROUP BY dept_id and OVER (PARTITION BY dept_id)?

Question 5

5.Why write 100.0 * salary / SUM(salary) OVER () instead of 100 * salary / SUM(salary) OVER () in SQLite?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

employees has 11 rows. How many rows does SELECT name, AVG(salary) OVER (PARTITION BY dept_id) FROM employees return?

Answer: 11

A window function never removes rows; each of the 11 rows gets its department average.

What does SUM(salary) OVER () put on each row?

Answer: The total of all salaries in the result

Empty brackets make the window the whole result, so every row shows the same grand total.

A query has WHERE dept_id = 2 and SUM(salary) OVER (). What does the sum cover?

Answer: Only the Sales rows that passed the WHERE

Window functions run after WHERE, so they only see the filtered rows.

What is the main difference between GROUP BY dept_id and OVER (PARTITION BY dept_id)?

Answer: GROUP BY collapses each group into one row; PARTITION BY keeps every row

Both split rows into groups, but only GROUP BY squeezes them together.

Why write 100.0 * salary / SUM(salary) OVER () instead of 100 * salary / SUM(salary) OVER () in SQLite?

Answer: 100.0 makes the division keep decimals instead of dropping them

Integer divided by integer drops the fraction in SQLite and PostgreSQL.

Two employees have dept_id NULL. After GROUP BY dept_id, where are they?Related

Answer: In one NULL group together

All NULLs fall into a single group.

From GROUP BY

In SQL Server, an INTEGER column holds 1 and 2. What does AVG return?Related

Answer: 1

SQL Server returns an integer average for integer input. Use AVG(col * 1.0) there.

From Aggregate functions

What does SELECT city, SUM(amount) FROM orders GROUP BY city return?Related

Answer: One row per city with its total

GROUP BY city makes one group per city, and SUM runs on each group.

From Counting and totals: aggregate functions

Inside WHERE salary > (SELECT AVG(salary) FROM employees WHERE dept_id = dept_id), what does dept_id = dept_id compare?Related

Answer: The inner row with itself, so it is always true (except for NULLs)

Unqualified names refer to the nearest table. Use aliases: e2.dept_id = e.dept_id.

From Correlated subqueries and derived tables

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Window functions” before its exam: Ranking: ROW_NUMBER, RANK, DENSE_RANK and NTILE, Running totals, moving averages and frames, LAG, LEAD, FIRST_VALUE and LAST_VALUE, Top-N per group and de-duplication.
  2. Module 7 exam: Window functions · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Changing data, transactions and ACID. 62 topics left in the course before the final exam.

← Previous: UNION, INTERSECT and EXCEPT