The Premium Shelf

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

Domain
SQL
Difficulty
medium
Seniority
L3
Asked in
IBM

Problem

A product's average unit price is the dollars it sold divided by the units it sold, so every unit counts once no matter how large the order it came in. For the merchandising team's premium shelf, list every product whose average unit price across the transactions table reaches $100, with its name from the products table, lowest product ID first.

The catalog's high end, weighed one unit at a time.

Tables

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, 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

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

Expected output

First 5 of 71 rows
product_idproduct_nameavg_unit_price
1442premium widget 10x144.69
1687essential set 15x212.04
1736classic system 16x112.755
1932premium widget 20x279.39
1981basic device 21x146.43

Worked solution and explanation

What this problem is really testing

Strip the retail costume and this is a weighted average hiding behind a catalog query. 'Average unit price' is not the mean of each sale's own unit price; it is total dollars over total units, SUM(total_amount) / SUM(quantity). Anyone can join the two tables and group by product. What separates candidates is seeing that a product with one five-unit order and one single-unit order cannot be averaged row by row without over-weighting the small order. Reach for AVG(total_amount / quantity) and every product with mixed order sizes drifts off its true unit economics, so the wrong products slip through the $100 gate.


Break down the requirements

Step 1: Join transactions to products

Inner-join transactions to products on product_id. The join is what guarantees 'has been bought': only products that appear in transactions survive it, so no separate count predicate is needed, and unsold products never show up as null-priced rows. Sales whose product_id is null match nothing and drop out too.

Step 2: Group by product

GROUP BY the product (product_id plus its product_name) to collapse all of a product's purchases into one row before the metric is computed.

Step 3: Weight the average, then filter

Compute the unit-weighted average as SUM(total_amount) / SUM(quantity): total dollars across all purchases divided by total units, not the average of per-sale unit prices. Put that same expression in HAVING ... >= 100 so only products clearing the $100 gate remain, then emit LOWER(product_name) and sort by product_id.


The solution

Weighted unit price with a HAVING gate
SELECT t.product_id,
       LOWER(p.product_name) AS product_name,
       SUM(t.total_amount) / SUM(t.quantity) AS avg_unit_price
FROM transactions t
JOIN products p ON t.product_id = p.product_id
GROUP BY t.product_id, p.product_name
HAVING SUM(t.total_amount) / SUM(t.quantity) >= 100
ORDER BY t.product_id
Wrong: average of ratios
AVG(total_amount / quantity) averages each sale's own per-unit price equally, so a one-unit impulse buy counts as much as a five-unit bulk order. Products with lumpy order sizes report a price that matches none of their sales.
Right: ratio of sums
SUM(total_amount) / SUM(quantity) pools every dollar and every unit first, then divides once. Big orders pull the average in proportion to how much they actually sold, which is the unit economics the $100 gate is meant to measure.

Common follow-up questions