Top Average By Region
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
- Sanofi
Problem
Pull the three product categories with the highest average transaction amount, using data from both the transactions and products tables. Return each category and its average, from highest to lowest.
Region by region, who pulls the best average?
Tables
transactions
Columns: transaction_id INTEGER, product_id INTEGER, total_amount REAL, user_id INTEGER, quantity INTEGER, transaction_date TEXT
| transaction_id | user_id | product_id | quantity | total_amount | transaction_date |
|---|---|---|---|---|---|
| 1067 | 197 | 1001 | 2 | 23.46 | 2026-02-02 |
| 1134 | 294 | 1050 | 3 | 36.93 | 2026-03-03 |
| 1201 | 391 | 1099 | 4 | 50.4 | 2026-04-04 |
| 1268 | 488 | 1148 | 5 | 63.87 | 2026-05-05 |
| 1335 | 585 | 1197 | 0 | 0 | 2026-06-06 |
| 1402 | 682 | 1246 | 2 | 90.81 | 2026-07-07 |
| 1469 | 779 | 1295 | 3 | 104.28 | 2026-08-08 |
| 1536 | 876 | 1344 | 4 | 117.75 | 2026-09-09 |
| 1603 | 973 | 1393 | 5 | -45 | 2026-01-10 |
| 1670 | 1070 | 1442 | 1 | 144.69 | 2026-02-11 |
products
Columns: product_id INTEGER, category TEXT, product_name TEXT, price REAL, rating REAL, in_stock INTEGER
| product_id | product_name | category | price | rating | in_stock |
|---|---|---|---|---|---|
| 1001 | Basic Device 1X | Books | 17.52 | 2.7 | 1 |
| 1050 | Deluxe Bundle 2X | Clothing | 25.05 | 4.4 | 1 |
| 1099 | Pro Unit 3X | Home & Kitchen | 32.58 | 2.1 | 1 |
| 1148 | Ultra Tool 4X | Collectibles | 40.11 | 3.8 | 1 |
| 1197 | Essential Set 5X | Toys | 47.64 | 1.5 | 1 |
| 1246 | Classic System 6X | Beauty | 0 | 3.2 | 1 |
| 1295 | Smart Gadget 7X | Automotive | 62.7 | 4.9 | 0 |
| 1344 | Eco Kit 8X | Garden | 70.23 | 2.6 | 1 |
| 1393 | Turbo Pack 9X | Music | 77.76 | 4.3 | 0 |
| 1442 | Premium Widget 10X | Electronics | 85.29 | 2 | 1 |
Expected output
| category | avg_amount |
|---|---|
| Sports | 833.5842857142858 |
| Electronics | 750.8399999999999 |
| Beauty | 700.9217647058823 |
Worked solution and explanation
What this really is
This is a weighted average across a join, dressed up as a category leaderboard. The category lives only in products and the money only in transactions, so the join is the easy part. What separates candidates is the grain the average is taken at. Average the joined sale rows and every sale gets one vote. Average per product_id first and then average those per category, and every product gets one vote instead. On this data that flips the answer: 'Beauty', where one product has a single linked sale instead of two, falls from 700.92 to about 674.51, and 'Books' slides into third. Same columns, same shape, wrong leaderboard.
Getting there
Step 1: Put a category on every sale
Inner-join transactions to products on product_id. A sale with an empty product_id matches nothing and drops out. That is the right outcome: it has no category to be averaged into.
Step 2: Average the joined rows, not the products
Group by p.category and take AVG(t.total_amount) directly over the joined rows. One sale is one row, so one sale is one vote. The zero-dollar voided sale and the refund stay in, because they are real sales. 'Beauty' ends up averaging 17 rows.
Step 3: Sort, then cut at three
Sort on avg_amount DESC, then apply LIMIT 3. The p.category ASC key only matters if two averages tie exactly. Here the cut falls between 'Beauty' at 700.92 and 'Automotive' at 698.54, so that key stays in reserve.
SELECT p.category AS category, AVG(t.total_amount) AS avg_amount FROM transactions t JOIN products p ON t.product_id = p.product_id GROUP BY p.category ORDER BY avg_amount DESC, p.category ASC LIMIT 3- Average of sales
AVG(t.total_amount)over the joined rows. 'Beauty' averages 17 sales to 700.92 and takes third.- Average of product averages
AVG(product_avg)over one row perproduct_id. 'Beauty' averages 9 products to about 674.51, and 'Books' at 681.99 takes third.
WITH product_avgs AS (
SELECT product_id, AVG(total_amount) AS product_avg
FROM transactions
GROUP BY product_id
)
SELECT p.category, AVG(pa.product_avg) AS avg_amount
FROM product_avgs pa
JOIN products p ON pa.product_id = p.product_id
GROUP BY p.category
ORDER BY avg_amount DESC
LIMIT 3Looks like a harmless pre-aggregation, but it returns 'Books' in place of 'Beauty'.
Common follow-up questions
- How would you get the same answer if `transactions` were pre-rolled to one row per `product_id` per day? (Tests whether you carry sums and counts instead of averages.)
- Should refunds like the -45.00 sale count toward a category's average? (Tests whether you ask about the business rule before filtering on `total_amount`.)
- How would you report how much revenue the inner join left out because `product_id` was empty? (Tests reasoning about unmatched join keys.)
- What happens at the cut if the fourth category ties the third exactly? (Tests what the `category` key does just before `LIMIT 3`.)