The Crown Jewels

An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
easy
Seniority
L5
Asked in
FinThrive

Problem

Across our whole catalog and all of its sales history in transactions, we want the products that have brought in the most money. List the top 5 by total revenue with their names from products, biggest earner first.

Revenue crowns the winners. Who sold the most?

Tables

products

Columns: product_id INTEGER, product_name TEXT, category TEXT, price REAL, rating REAL, in_stock INTEGER

products: 10 of 200 rows
product_idproduct_namecategorypriceratingin_stock
1001Basic Device 1XBooks17.522.71
1050Deluxe Bundle 2XClothing25.054.41
1099Pro Unit 3XHome & Kitchen32.582.11
1148Ultra Tool 4XCollectibles40.113.81
1197Essential Set 5XToys47.641.51
1246Classic System 6XBeauty03.21
1295Smart Gadget 7XAutomotive62.74.90
1344Eco Kit 8XGarden70.232.61
1393Turbo Pack 9XMusic77.764.30
1442Premium Widget 10XElectronics85.2921

transactions

Columns: transaction_id INTEGER, product_id INTEGER, total_amount REAL, user_id INTEGER, quantity INTEGER, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 5 rows
product_nametotal_revenue
Premium Widget 100X2713.98
Eco Kit 98X2660.1
Smart Gadget 97X2633.16
Classic System 96X2606.22
Essential Set 95X2579.28

Worked solution and explanation

What this really is

This is a sum-per-group wearing a leaderboard costume, and the word revenue is doing more work than it looks. Revenue is SUM(total_amount): COUNT(*) ranks products by how often they sell, and AVG(total_amount) ranks them by ticket size. On this data nearly every product sold exactly twice, so a count-based list is a pile of ties, and an average reports half the money each leader actually brought in. There is a quieter trap underneath: group by product_name and two distinct products that happen to share a name fuse into one inflated row. Miss either and you ship the wrong winners or the wrong numbers with a query that looks perfectly clean.


Working through it

Step 1: Join and carry the name

Each transaction stores only product_id, so join transactions to products on product_id to carry product_name alongside every sale. An inner join is the right call: a product with no sales cannot be a revenue leader, and sales with a missing product_id cannot be credited to anyone, so dropping both costs nothing here.

Step 2: Sum the money, do not count the rows

total_amount is the dollar value of each sale, refunds and voided sales included, so SUM(t.total_amount) per product is revenue. COUNT(*) answers 'sold most often' and AVG answers 'biggest ticket', and neither is the question being asked.

Step 3: Group on identity, then take the top five

Group by product_id (carrying product_name for display) so same-named products stay separate, sort by revenue highest first, and cap the result at 5. Grouping on the name alone would silently merge distinct products into one row.


The solution

Join products to transactions, sum revenue per product, top 5 by total
SELECT p.product_name, SUM(t.total_amount) AS total_revenue
FROM products p
JOIN transactions t ON t.product_id = p.product_id
GROUP BY p.product_id, p.product_name
ORDER BY total_revenue DESC
LIMIT 5

Common follow-up questions