Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How do I select the top three items within each category?

Quick answer

In SQLite, calculate row_number() for each category, ordering by sales from highest to lowest, then filter to positions 1 through 3 in an outer query. Add a unique item ID as a tie-breaker for a consistent three-item cutoff. If you want to include every item tied at third place, use rank() instead; that can return more than three items in a category. [S1]

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have an items table with category, item_id, item_name, and sales columns. How do I get the three highest-selling items in each category in SQLite?

Geez

Rank the rows within each category, then filter the ranked results in an outer query. For exactly three positions per category, use row_number() and include a stable tie-breaker such as a unique item ID. SQLite assigns consecutive row numbers according to the window ordering, and PARTITION BY category starts that ordering separately for each category. [S1]

Assuming those column names and that larger sales values mean higher sales, the query is:

WITH ranked_items AS (
  SELECT
    category,
    item_id,
    item_name,
    sales,
    row_number() OVER (
      PARTITION BY category
      ORDER BY sales DESC, item_id
    ) AS row_num
  FROM items
)
SELECT category, item_id, item_name, sales, row_num
FROM ranked_items
WHERE row_num <= 3
ORDER BY category, row_num;

The CTE gives each row a position within its category; the outer WHERE keeps positions 1, 2, and 3. The final ORDER BY controls how the selected rows are displayed: window ordering alone does not guarantee the output display order. [S1]

Follow-up question

Can you show what the query returns, and what if a category has fewer than three items?

Geez

Here is a small invented example, assuming these are the sales records:

category  item_id  item_name  sales
Books     1        Atlas      90
Books     2        Fern       75
Books     3        Harbor     75
Books     4        Kite       60
Games     5        Orbit      42
Games     6        Sable      35

With the query’s ORDER BY sales DESC, item_id, the selected rows would be:

category  item_id  item_name  sales  row_num
Books     1        Atlas      90     1
Books     2        Fern       75     2
Books     3        Harbor     75     3
Games     5        Orbit      42     1
Games     6        Sable      35     2

The item ID settles the order between Fern and Harbor, which have equal sales. Games has only two rows, so both appear; the cutoff does not add rows that are not present. This example illustrates the query’s behavior and is not a report of real sales.

Follow-up question

I need every item tied for third place, even if that means returning more than three rows. What should I change?

Geez

Use rank() instead of row_number() and order the window by sales alone, then filter to ranks up to three:

WITH ranked_items AS (
  SELECT
    category,
    item_id,
    item_name,
    sales,
    rank() OVER (
      PARTITION BY category
      ORDER BY sales DESC
    ) AS sales_rank
  FROM items
)
SELECT category, item_id, item_name, sales, sales_rank
FROM ranked_items
WHERE sales_rank <= 3
ORDER BY category, sales_rank, item_id;

With rank(), tied rows share a rank, and the next rank can be skipped. So if two items tie at rank 3, both are included; if a tie at an earlier rank causes the next rank to be 4, rows at rank 4 are excluded. The result may contain more than three items in a category, or fewer, depending on the sales values. [S1]

Follow-up question

How do I decide which version fits, and what should I check before using it?

Geez

Choose based on what “top three” means for your task. Use row_number() when you need no more than three rows per category and can accept a tie-break rule. Use rank() when every item with a qualifying sales rank should remain, including ties at the cutoff. If ties matter but you have not decided how to handle them, confirm that requirement before relying on either result.

Before running the query, check that the table and column names match your database, that the sales column represents the measure you want to rank, and that your chosen tie-breaker is suitable. The examples assume item_id exists and can consistently order equal-sales rows. The SQLite documentation establishes the described window-function behavior; it does not establish your table’s schema, the meaning of its sales values, or which tie policy is right for your use case. [S1]

A practical checklist

  • Confirm the table name and the category, item ID, item name, and sales columns.

  • Decide whether the cutoff means exactly three positions or all items tied at third place.

  • For a three-position cutoff, use row_number() with ORDER BY sales DESC and a suitable tie-breaker.

  • For a tie-inclusive cutoff, use rank() ordered by sales and filter with sales_rank <= 3.

  • Add an outer ORDER BY if the displayed row order matters.

A few more useful answers

Why include `item_id` in the `row_number()` ordering?

It provides a tie-breaker when sales values match, so tied items receive distinct positions in a consistent order. Choose a column that makes sense for your data; the example assumes item_id is suitable. [S1]

Can the query return fewer than three items for a category?

Yes. If a category has fewer than three rows, the filter returns the rows available. The cutoff selects existing rows; it does not create additional ones.

Does ordering inside the window sort the final results?

No. Window ordering determines ranking, while the outer query’s ORDER BY controls display order. Add an outer ordering clause if you want results grouped and ranked visibly. [S1]

Sources

References used for this guide.

  1. [S1] SQLite window functions: partitioned row numbering and ranking
    sqlite.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work