DEV Community

Cover image for SQL Pattern Series #14: The Top-Per-Group Pattern
Baldwin Apps
Baldwin Apps

Posted on

SQL Pattern Series #14: The Top-Per-Group Pattern

Finding the most important row within each group

SQL Pattern Series #14 of 21

A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.

What You'll Learn

In this article you'll learn:

  • How to find the top row within each group
  • Why ROW_NUMBER() is commonly used for this problem
  • How partitioning creates independent rankings
  • When to use the Top-Per-Group Pattern

Many SQL problems involve finding a single "best" row.

For example:

  • The latest order for each customer
  • The highest sale in each region
  • The newest status update for each ticket
  • The most recent login for each user

The challenge is that you don't want the top row overall.

You want the top row within each group.

That is where the Top-Per-Group Pattern becomes useful.


The Problem

Imagine an Orders table:

CustomerID OrderDate
1 2024-01-15
1 2024-02-10
2 2024-01-20
2 2024-03-01

Suppose you need:

The most recent order for each customer.

A simple ORDER BY won't solve the problem.

You need to rank rows separately within each customer group.



The Top-Per-Group Pattern

The most common solution uses:

ROW_NUMBER() OVER (
    PARTITION BY column
    ORDER BY column DESC
)
Enter fullscreen mode Exit fullscreen mode

The window function creates a ranking inside each group.

The highest-ranked row receives:

rn = 1
Enter fullscreen mode Exit fullscreen mode

Those are the rows we keep.


Example

SELECT *
FROM (
    SELECT
        o.*,
        ROW_NUMBER() OVER (
            PARTITION BY o.CustomerID
            ORDER BY o.OrderDate DESC
        ) AS rn
    FROM Orders o
) t
WHERE rn = 1;
Enter fullscreen mode Exit fullscreen mode

This query:

  1. Groups rows by customer.
  2. Orders each customer's rows from newest to oldest.
  3. Assigns row numbers.
  4. Returns only the first row in each group.

Example Result

CustomerID OrderDate
1 2024-02-10
2 2024-03-01

Each customer now has exactly one row.

The most recent one.


Why This Pattern Matters

Many reporting and analytics tasks require a single representative row.

Examples include:

  • latest customer activity
  • newest support ticket update
  • highest scoring transaction
  • most recent account balance
  • top-performing product per category

The Top-Per-Group Pattern solves these problems consistently.


Partitioning Creates Independent Rankings

The key idea is:

PARTITION BY CustomerID
Enter fullscreen mode Exit fullscreen mode

Without partitioning:

ROW_NUMBER()
Enter fullscreen mode Exit fullscreen mode

would rank all rows together.

With partitioning:

ROW_NUMBER()
OVER (
    PARTITION BY CustomerID
)
Enter fullscreen mode Exit fullscreen mode

each customer receives their own ranking sequence.

Think of it as creating a separate leaderboard for each group.


A Note on Ties

Suppose two orders have the exact same timestamp.

Which row should become:

rn = 1
Enter fullscreen mode Exit fullscreen mode

Without a tie-breaker, the database may choose one arbitrarily.

For deterministic results, consider adding another column:

ROW_NUMBER() OVER (
    PARTITION BY CustomerID
    ORDER BY OrderDate DESC,
             OrderID DESC
)
Enter fullscreen mode Exit fullscreen mode

This guarantees a predictable winner.


ROW_NUMBER vs RANK

The Top-Per-Group Pattern typically uses:

ROW_NUMBER()
Enter fullscreen mode Exit fullscreen mode

because it guarantees exactly one row receives:

rn = 1
Enter fullscreen mode Exit fullscreen mode

Functions such as:

RANK()
Enter fullscreen mode Exit fullscreen mode

or:

DENSE_RANK()
Enter fullscreen mode Exit fullscreen mode

may return multiple rows when ties occur.

That behavior can be useful, but it solves a slightly different problem.


When I Reach for This Pattern

I typically use the Top-Per-Group Pattern when:

  • I need the latest row per entity
  • I need the highest value per category
  • I need one representative row from each group
  • ranking matters within groups
  • duplicate historical records exist

Examples include:

  • latest order per customer
  • newest employee record
  • highest sale per region
  • most recent account activity
  • latest status change per ticket

Key Takeaway

Many SQL problems are really asking:

Which row wins within each group?

The Top-Per-Group Pattern answers that question.

Create a ranking.

Partition by the group.

Keep:

rn = 1
Enter fullscreen mode Exit fullscreen mode

Sometimes finding the best row is easier than aggregating them all.


SQL Pattern Series

This article is part of the SQL Pattern Series, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.

Previous articles:

  • SQL Pattern Series #1: The Presence Pattern
  • SQL Pattern Series #2: The Match Pattern
  • SQL Pattern Series #3: The Missing Data Pattern
  • SQL Pattern Series #4: The Moving Sum Pattern
  • SQL Pattern Series #5: The Deduplication Pattern
  • SQL Pattern Series #6: The Routing Pattern
  • SQL Pattern Series #7: The Running Total Pattern
  • SQL Pattern Series #8: The Query Order Pattern
  • SQL Pattern Series #9: The Period-over-Period Pattern
  • SQL Pattern Series #10: The Hierarchy Pattern
  • SQL Pattern Series #11: The Merge Pattern
  • SQL Pattern Series #12: The Fallback Pattern
  • SQL Pattern Series #13: The Gap Detection Pattern

SQL Bubble Pop

If you are learning SQL or helping others learn SQL, I created SQL Bubble Pop, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.

The goal is simple:

Learn SQL by recognizing patterns instead of memorizing syntax.

Top comments (0)