Use ROW_NUMBER() to rank rows within each group, then filter by row number to retrieve the top N records per group — for example, the lowest-paid employees in each job category.
Sample data
The examples in this topic use the following emp table.
| empno | ename | job | sal |
|---|---|---|---|
| 7369 | SMITH | CLERK | 800.0 |
| 7876 | SMITH | CLERK | 1100.0 |
| 7900 | JAMES | CLERK | 950.0 |
| 7934 | MILLER | CLERK | 1300.0 |
| 7499 | ALLEN | SALESMAN | 1600.0 |
| 7654 | MARTIN | SALESMAN | 1250.0 |
| 7844 | TURNER | SALESMAN | 1500.0 |
| 7521 | WARD | SALESMAN | 1250.0 |
Implementation
Query the row ID of each record and use the WHERE clause to filter the records.
SELECT * FROM (
SELECT empno
, ename
, sal
, job
, ROW_NUMBER() OVER (PARTITION BY job ORDER BY sal) AS rn
FROM emp
) tmp
WHERE rn < 10;How it works
Assign a row number to each record within its group using ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...), then wrap the query in a subquery and apply a WHERE filter on the row number.
PARTITION BY job— resets the row number counter for each distinctjobvalueORDER BY sal— determines the ranking order within each partition (ascending by default, so the lowest salary gets row number 1)WHERE rn < N + 1— keeps only the first N rows from each group; replaceNwith your target count (for example,WHERE rn < 4returns the top 3 records per group)
Query
SELECT * FROM (
SELECT empno
, ename
, sal
, job
, ROW_NUMBER() OVER (PARTITION BY job ORDER BY sal) AS rn
FROM emp
) tmp
WHERE rn < 10;Expected output
| empno | ename | job | sal | rn |
|---|---|---|---|---|
| 7369 | SMITH | CLERK | 800.0 | 1 |
| 7900 | JAMES | CLERK | 950.0 | 2 |
| 7876 | SMITH | CLERK | 1100.0 | 3 |
| 7934 | MILLER | CLERK | 1300.0 | 4 |
| 7654 | MARTIN | SALESMAN | 1250.0 | 1 |
| 7521 | WARD | SALESMAN | 1250.0 | 2 |
| 7844 | TURNER | SALESMAN | 1500.0 | 3 |
| 7499 | ALLEN | SALESMAN | 1600.0 | 4 |
When two rows have the samesalvalue (for example, MARTIN and WARD both have 1250.0),ROW_NUMBER()assigns them distinct row numbers in an arbitrary order. If you need consistent tie-breaking, add a tiebreaker column to theORDER BYclause (for example,ORDER BY sal, empno). To include all tied records rather than break ties, useRANK()orDENSE_RANK()instead.