DevLearningTools

LEARN · SQL

SQL Window Functions

A window function calculates across a set of related rows without collapsing them into one, the way GROUP BY does. Learn the OVER and PARTITION BY clauses, and exactly how this differs from GROUP BY.

HR wants every employee's salary shown next to their department's average, so each person can see where they stand, without losing any individual rows the way group-by-department would. That's precisely what a window function does and GROUP BY can't.

Learning Objectives

  • Define a window function precisely, and state exactly how it differs from GROUP BY.
  • Write a window function using OVER and PARTITION BY.
  • Know the minimum version each database in this course needs for window function support.

All Rows Kept

Unlike GROUP BY

PARTITION BY

Rows grouped for the calculation only

Window Function

AVG(), RANK(), and others

Per-Row Result

Attached alongside the original row

What a Window Function Actually Is

A window function performs a calculation across a set of rows related to the current row, its "window", without collapsing those rows into fewer output rows. This is the one thing that actually distinguishes it from GROUP BY: GROUP BY department_id turns many employee rows into one row per department, a window function keeps every single employee row, and just attaches a calculated value (like that department's average) alongside each one.

GROUP BYCollapses rows
  • One output row per group
  • Individual row detail is lost
  • Can't show a group's average next to each employee's own row
Window FunctionKeeps every row
  • Same number of output rows as input
  • Every original column is still there
  • A group's average sits right alongside each employee's own salary

OVER and PARTITION BY

OVER () is what turns AVG() into a window function instead of a regular aggregate, PARTITION BY department_id tells it to compute each employee's dept_avg_salary using only that employee's own department's rows, exactly like a GROUP BY department_id would group for an aggregate, but without collapsing anything. Leave PARTITION BY out entirely and the window covers every row in the whole table.

MySQL / MariaDB / PostgreSQL / SQL Server / Oracle / SQLite
SELECT first_name, last_name, department_id, salary,
       AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary
FROM employees;

Minimum Version for Window Function Support

DatabaseMinimum Version
MySQL8.0 (2018)
MariaDB10.2 (2017)
PostgreSQLLong-standing support, 8.4 (2009) onward
SQL Server2012, for the full feature set used in this course
OracleLong-standing support, 8i/9i onward
SQLite3.25.0 (2018)
NOTE

Every current, actively supported version of all six databases has full window function support, this table only matters for an older, pinned production version. An unusually old MySQL (pre-8.0) or SQLite (pre-3.25) specifically is the one real place this still bites in practice.

Try It Yourself

Write a query showing every employee's first_name, salary, and the overall company-wide average salary alongside each row, on PostgreSQL.

Answer
SELECT first_name, salary,
       AVG(salary) OVER () AS company_avg_salary
FROM employees;

Frequently Asked Questions

Can a query use a window function and a regular GROUP BY at the same time?

Yes, and it's common: GROUP BY runs first and collapses rows into groups, then a window function can run over the already-grouped results. The reverse framing is more common in practice though, keeping every individual row and attaching a window calculation, specifically because that's the one thing GROUP BY alone can't do.

Does leaving out PARTITION BY make the window function run per-row?

No, the opposite, it makes the window cover every row in the entire result set as one single partition. AVG(salary) OVER () with no PARTITION BY computes one company-wide average, repeated on every row, not one average per row.

Common Beginner Mistakes

Assuming a window function needs a GROUP BY to work

It doesn't, that's the entire point of a window function, PARTITION BY inside OVER() does the grouping for the calculation, with no GROUP BY clause anywhere in the query and every original row still present in the output.

Confusing PARTITION BY with GROUP BY and expecting fewer output rows

PARTITION BY only changes which rows are grouped together for the calculation itself, it never reduces the number of rows returned. A query with PARTITION BY department_id on a 20-row employees table still returns all 20 rows.

Best Practices

  • Reach for a window function the moment a query needs a group-level value alongside individual row detail, that combination is exactly what GROUP BY can't do alone.
  • Check the target database's actual version against the minimum-version table before relying on window functions in a long-lived production system.
  • Name window function result columns descriptively (dept_avg_salary, not avg1), they sit alongside plain columns and should read clearly.

Interview Questions

What's the fundamental difference between a window function and GROUP BY?

GROUP BY collapses multiple rows into one output row per group, individual row detail is gone. A window function keeps every original row in the output and attaches a calculated value to each one, computed over a related set of rows (the "window", defined by PARTITION BY). SELECT department_id, AVG(salary) FROM employees GROUP BY department_id; returns one row per department; SELECT *, AVG(salary) OVER (PARTITION BY department_id) FROM employees; returns every employee row, each with that department's average attached.

Write a query showing each employee's salary alongside their department's average salary, without losing any employee rows.

SELECT first_name, last_name, department_id, salary, AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary FROM employees; — PARTITION BY department_id computes the average using only each employee's own department's rows, and because it's a window function, not a GROUP BY, every individual employee row is still present in the result.

Summary

A window function calculates across a related set of rows without collapsing them, the one real distinction from GROUP BY. OVER() marks a function as a window function, PARTITION BY defines which rows belong together for that calculation, and every database in this course has supported the feature for years in its current form.

What's Next?

SQL ROW_NUMBER() is next: the simplest window function, giving every row within a partition a unique, sequential number.

</>

Ready to Practice SQL?

Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.

SQLite / PostgreSQL

Fast, no server, no ads.

Open DB Fiddle ↗

MySQL / SQL Server

Switch databases with one dropdown.

Open SQL Fiddle ↗

Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.

Or try 50 SQL practice questions →