SQL window functions with ROW_NUMBER examples
SQL window functions calculate values across related rows without collapsing those rows into a single grouped result. This ROW_NUMBER example shows how PARTITION BY and window ordering solve a top-N-per-group problem.
Updated · SQL Arena team
What you will practice
- ROW_NUMBER, RANK, and DENSE_RANK
- PARTITION BY and window ordering
- LAG and LEAD
- Running totals with SUM() OVER
- Top-N rows per group
A practice problem in the trainer
This is how a problem looks in SQL Arena: the task, the editor, the result and the database schema.
Return the two employees with the highest salary in every department. Output employee_name, department_name, and salary. If two employees have the same salary, use the employee id as an additional ordering key.
WITH ranked AS ( SELECT e.*, ROW_NUMBER() OVER ( PARTITION BY department_id ORDER BY salary DESC, id ) AS rn FROM employees e ) SELECT r.employee_name, d.department_name, r.salary FROM ranked r JOIN departments d ON d.id = r.department_id WHERE r.rn <= 2 ORDER BY d.id, r.rn;
| # | employee_name | department_name | salary |
|---|---|---|---|
| 1 | Nora | Design | 81000 |
| 2 | Mia | Design | 72000 |
| 3 | Ethan | Finance | 93000 |
| 4 | Ivy | Finance | 87000 |
Return the two employees with the highest salary in every department. Output employee_name, department_name, and salary. If two employees have the same salary, use the employee id as an additional ordering key.
WITH ranked AS ( SELECT e.*, ROW_NUMBER() OVER ( PARTITION BY department_id ORDER BY salary DESC, id ) AS rn FROM employees e ) SELECT r.employee_name, d.department_name, r.salary FROM ranked r JOIN departments d ON d.id = r.department_id WHERE r.rn <= 2 ORDER BY d.id, r.rn;
How SQL window functions work with ROW_NUMBER
A regular ORDER BY salary DESC ranks the company as one list, which is not what the task asks for. GROUP BY does not solve it either. Grouping is useful when you want one aggregate row per department, such as MAX(salary), but here we need to keep individual employee rows and choose two of them from every department.
The ranked CTE calculates ROW_NUMBER() for each employee. PARTITION BY department_id restarts the numbering for every department. ORDER BY salary DESC places the highest salary first inside that department. Adding id as a second sort key removes ambiguity when two salaries are equal, so the numbering remains deterministic.
The outer query filters the generated row numbers with rn <= 2. A separate query level is useful because a window value is calculated after the WHERE phase of the same SELECT. The query then joins departments to return a readable department name. The final ORDER BY controls output presentation; it does not decide which employees belong to the top two.
A SQL window function operates over the rows defined by its OVER clause. PARTITION BY separates rows into independent partitions, while ORDER BY inside the window determines the sequence used by functions such as ROW_NUMBER, RANK, or running aggregates. Unlike GROUP BY, window functions keep the original rows in the result. That makes them useful for rankings, running totals, comparisons with previous or next rows, and top-N-per-group queries where individual records still need to be returned.
WITH ranked AS ( SELECT e.*, ROW_NUMBER() OVER ( PARTITION BY department_id ORDER BY salary DESC, id ) AS rn FROM employees e ) SELECT r.employee_name, d.department_name, r.salary FROM ranked r JOIN departments d ON d.id = r.department_id WHERE r.rn <= 2 ORDER BY d.id, r.rn;
| employee_name | department_name | salary |
|---|---|---|
| Nora | Design | 81000 |
| Mia | Design | 72000 |
| Ethan | Finance | 93000 |
| Ivy | Finance | 87000 |
| Zoe | Support | 66000 |
| Liam | Support | 61000 |
Common SQL window function mistakes
Leaving out PARTITION BY
Without PARTITION BY department_id, ROW_NUMBER creates one sequence across the entire employee table. Filtering to the first two rows would return only two employees overall instead of two from each department.
Using RANK when exactly N rows are required
RANK can return more than N rows when several records tie at the boundary. ROW_NUMBER always assigns a distinct position, which makes it a better fit when the requirement literally says to return exactly two records per group.
Using an incomplete window order
If salary is the only ordering expression, equal salaries have no defined internal order. Add a stable tie-breaker such as a primary key when the exact row sequence matters.
Practice problems on this topic
Window functions trackAfter this ROW_NUMBER exercise, useful window functions practice problems include numbering purchases per user, measuring gaps between events, detecting session starts, calculating moving averages, and building running revenue totals. SQL Arena groups the topic in the “Window Functions from Start to Finish” track. The trainer contains 1,192 tasks, with separate browser sandboxes for PostgreSQL, MySQL, ClickHouse, and BigQuery. Each task is checked against hidden data rather than only the visible sample, so a query has to express the general rule. The free plan requires no card and includes 292 tasks across all four dialects. EXPLAIN and schema ER diagrams are available when you need to inspect the query or tables more closely.
- EasyfreePurchase sequence number per userclickhouse
- EasyfreeCrew Size per Workerpostgresqlmysqlclickhouse
- EasyfreeGaps between playspostgresql
- EasyfreeSession-start flag in an online storepostgresql
- MediumPremiumA Moving Average Receiptbigquery
- MediumPremiumRevenue as a Running Totalbigquery
- MediumPremiumEvents from the Most Active Usersbigquery
- MediumPremiumEvery User’s First Movebigquery
Reference on this topic
In-depth articles with syntax and examples in the SQL Arena reference.
Frequently asked questions
What are SQL window functions?
SQL window functions calculate a value using a set of rows related to the current row while keeping each row in the result. They use an OVER clause and can include PARTITION BY and ORDER BY.
How do SQL window functions work?
The OVER clause defines the window used for the calculation. PARTITION BY can split the data into separate groups, and ORDER BY can define the sequence within each group.
What is ROW_NUMBER in SQL window functions?
ROW_NUMBER assigns a unique sequential number to rows according to the ordering defined inside OVER. If PARTITION BY is present, numbering restarts for every partition.
What is the difference between SQL window functions and GROUP BY?
GROUP BY reduces multiple rows to one row per group. Window functions preserve the individual rows and add calculations based on other rows in the same window.
How do you get the top N rows per group with SQL window functions?
A common approach is to calculate ROW_NUMBER in a CTE or subquery, partition by the grouping column, and order each partition by the ranking criterion. The outer query then filters the generated row number to values less than or equal to N.
Practice SQL window functions with exercises
Move from ROW_NUMBER to ranking, event gaps, running totals, and moving windows in SQL Arena. For a structured path through the topic, continue with the “Window Functions from Start to Finish” track.
Practice window functionsMore practice from CodeRang
- Python ArenaPython Arena has 1300+ Python 3.12 problems covering functions, classes, async code and technical interview tasks.1300+ tasks · Python 3.12
- Playwright ArenaPlaywright Arena has 30 JavaScript and E2E problems where you write a test and immediately see what it did in the browser.30 tasks · JavaScript / E2E