Blog

Row number SQL: Ranking Rows

Working with large databases can get overwhelming. It’s like trying to find your favorite donut in a box of 100! Thankfully, SQL gives us a cool feature called ROW_NUMBER() to keep things in order.

TL;DR

ROW_NUMBER() is a handy SQL function that lets you assign a unique rank to each row in your query result. It’s like labeling your data by order, but the fun part is choosing how that order works via ORDER BY. If you want to do pagination, find top items, or eliminate duplicates, this is your best friend. It’s simple, helpful, and once you start using it, you’ll wonder how you ever SQL-ed without it!

What Is ROW_NUMBER()?

ROW_NUMBER() is a window function in SQL. It assigns a unique number to each row in a result set. The numbering starts from 1 and goes up — 1, 2, 3, and so on.

But here’s the key — you decide the order in which rows are numbered. You do that using the ORDER BY clause.

Syntax

SELECT 
  column1,
  ROW_NUMBER() OVER (ORDER BY column2) AS row_num
FROM 
  table_name;

Simple, right? Let’s break it down:

  • ROW_NUMBER() is the function.
  • OVER (ORDER BY column2) tells SQL how to rank the rows.
  • AS row_num gives your row number column a name.

Why Should You Care?

Because it makes your life easier! Here are a few practical uses:

  • Find the top-selling product per category.
  • Paginate results for a web page.
  • Remove duplicate rows.
  • Find the latest entry for each user.

A Simple Example

Imagine you have a table of students and their test scores:

Name Score
Anna 95
Ben 92
Clara 87
Daniel 92

If you want to rank students by their scores, you could write:

SELECT 
  name,
  score,
  ROW_NUMBER() OVER (ORDER BY score DESC) AS rank
FROM 
  students;

This gives:

Name Score Rank
Anna 95 1
Ben 92 2
Daniel 92 3
Clara 87 4

Note that even though Ben and Daniel had the same score, their ranks are different — 2 and 3. That’s because ROW_NUMBER() always gives a unique number.

How Is It Different from RANK() and DENSE_RANK()?

This is super important, so let’s compare:

  • ROW_NUMBER(): Always unique. No skipping. Even if values tie.
  • RANK(): Ties share the same rank, and it skips numbers.
  • DENSE_RANK(): Ties share same rank, but don’t skip numbers.

Using our student table with RANK(), the result would be:

Name Score Rank()
Anna 95 1
Ben 92 2
Daniel 92 2
Clara 87 4

And with DENSE_RANK():

Name Score Dense Rank
Anna 95 1
Ben 92 2
Daniel 92 2
Clara 87 3

Pretty neat, huh?

Filtering for Top Items

Let’s say you want to find only the top-scoring student. You could wrap your query and filter like this:

WITH RankedStudents AS (
  SELECT 
    name,
    score,
    ROW_NUMBER() OVER (ORDER BY score DESC) AS rank
  FROM 
    students
)
SELECT 
  name, score
FROM 
  RankedStudents
WHERE 
  rank = 1;

That’s how you get only the best!

Partitioning Rows

ROW_NUMBER() also lets you restart the count for different groups using PARTITION BY.

Let’s say we have students from different classes:

Name Class Score
Anna A 95
Ben A 92
Clara B 90
Daniel B 85

We want to rank students within each class.

SELECT 
  name,
  class,
  score,
  ROW_NUMBER() OVER (PARTITION BY class ORDER BY score DESC) AS class_rank
FROM 
  students;

Which gives:

Name Class Score Class Rank
Anna A 95 1
Ben A 92 2
Clara B 90 1
Daniel B 85 2

ROW_NUMBER() resets for each class. Magic!

Common Use Cases

Here are some popular places where ROW_NUMBER() saves the day:

  • Pagination – Split query results into pages for a website.
  • De-duplication – Keep only one row when duplicates exist.
  • Top-N Queries – Get the best N items per category.
  • Change Detection – Know the latest or first entries per item.

Tips and Tricks

  • Always use ORDER BY or your row numbers may be meaningless.
  • Be careful when joining tables — <i