Oracle ROW_NUMBER Function

Number the rows of each group with ROW_NUMBER for top-N-per-group queries and deduplication, and make the numbering repeatable.

ROW_NUMBER gives each row of a result, or of each partition, a sequential number in the order you choose: 1, 2, 3, with no ties. It is the workhorse of top-N-per-group queries, deduplication, and pagination, and the analytic function you will use most.

Code for This Guide

The main examples are in the examples/analytic-functions folder of the Oracle Database 26ai code repository on GitHub, each with its output. They use NIMBUS, the sample schema of a fictional airline, which you can install with the scripts in the setup/nimbus folder.

They come from Oracle Database 26ai SQL and PL/SQL Book.

Syntax

Syntax:

row_number() over ([partition by expr [, ...]] order by expr [, ...])

PARTITION BY restarts the numbering for each group; ORDER BY decides the order. Rows that tie on the ORDER BY still get different numbers, in an order Oracle chooses.

ROW_NUMBER Compared with RANK and DENSE_RANK

This query numbers customers by their number of bookings with all three ranking functions.

Example:

select customer_id, count(*) as bookings,
       row_number() over (order by count(*) desc) as row_number,
       rank()       over (order by count(*) desc) as rank,
       dense_rank() over (order by count(*) desc) as dense_rank
from   bookings
group  by customer_id
order  by bookings desc, customer_id
fetch  first 9 rows only;

Output:

   CUSTOMER_ID    BOOKINGS    ROW_NUMBER    RANK    DENSE_RANK
______________ ___________ _____________ _______ _____________
             5          15             1       1             1
             1          12             2       2             2
             3          12             3       2             2
             4          12             7       2             2
            18          12             5       2             2
            21          12             6       2             2
            49          12             4       2             2
            11          11            11       8             3
            13          11            10       8             3

9 rows selected.

Six customers tie with 12 bookings. ROW_NUMBER gives them 2 to 7, in no particular order: customer 4 got 7 and customer 49 got 4. Run it again and the order among them may change. Add a tie-breaker, such as ORDER BY count(*) DESC, customer_id, whenever the numbers must be repeatable.

Top N per Group

Numbering each department's employees by salary and keeping the first two gives the two best-paid per department.

Example:

select department_id, last_name, salary
from   (select department_id, last_name, salary,
               row_number() over (partition by department_id order by salary desc) as rn
        from   employees)
where  rn <= 2 and department_id in (20, 30, 40)
order  by department_id, salary desc;

Output:

   DEPARTMENT_ID LAST_NAME       SALARY
________________ ____________ _________
              20 Clarke           27000
              20 Sato             25500
              30 Rahman           22000
              30 Taylor           11800
              40 Evans            34000
              40 Kapoor           21000

6 rows selected.

An inline view is needed because analytic functions cannot be used in WHERE; in Oracle AI Database 26ai, QUALIFY does the same without the subquery.

Keep One Row per Key

Deduplication is top-1 per group: number each customer's bookings from the latest, and keep number 1.

Example:

-- each customer's latest booking: number the bookings per customer, keep number 1
select customer_id, booking_ref, booked_at
from   bookings
where  customer_id in (1, 2, 3)
qualify row_number() over (partition by customer_id
                           order by booked_at desc, booking_id desc) = 1
order  by customer_id;

Output:

   CUSTOMER_ID BOOKING_REF    BOOKED_AT
______________ ______________ _______________________
             1 P2N7JP         23-FEB-2026 18:23:00
             2 QTJ2YH         12-FEB-2026 23:14:00
             3 4B2QFZ         07-MAR-2026 06:27:00

The second ORDER BY column, booking_id, breaks ties between bookings made at the same moment, so the result is deterministic.

Things to Know

  • ROW_NUMBER requires ORDER BY in the OVER clause.
  • For a simple first N rows of a whole result, FETCH FIRST n ROWS ONLY is simpler.
  • To delete duplicates, select their ROWIDs with ROW_NUMBER > 1 and delete those.

Related Guides

Conclusion

ROW_NUMBER numbers the rows of each partition 1, 2, 3 in the order of its ORDER BY, breaking ties arbitrarily. Add a unique tie-breaker for repeatable results, and filter on it, with an inline view or QUALIFY, for top-N per group and deduplication.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted