Market Share

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

Domain
SQL
Difficulty
hard
Seniority
L5

Problem

An investor slide claims every category earns nearly all its revenue from products we can ship today, and merchandising doesn't believe it: rebuild it from transactions and products as each category's percentage of its own revenue coming from products currently in stock, most in-stock first. The slide shows whole percentage points with the fraction dropped rather than rounded, alphabetical where two categories tie.

Every category wants a bigger slice.

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, 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

Expected output

First 5 of 11 rows
categoryin_stock_revenue_pct
Automotive98
Toys92
Clothing90
Beauty87
Electronics87

Worked solution and explanation

What this really is

This is a conditional part-to-whole ratio wearing an investor-deck costume, with a formatting rule that quietly decides the grade. Anyone can join transactions to products and sum revenue per category. What separates candidates is building the part and the whole in the same grouped pass, and then trimming it correctly. Filter out-of-stock rows with a WHERE and the denominator shrinks with the numerator, so every category reads 100, which is exactly the slide merchandising refused to believe. Then the share has to land in whole points with the fraction dropped: reach for ROUND and a 97.8 becomes 98.

Filter the rows
WHERE p.in_stock = 1 before grouping. The out-of-stock sales vanish from both sums, so numerator equals denominator and every category reads 100.
Filter inside the numerator
Keep every row and weight the numerator by p.in_stock. The denominator still sees the full category, so the share describes a real split.

Building it

Step 1: Attach category and stock status

Join transactions to products on product_id so every sale carries its category and its in_stock flag. Revenue is the total_amount already on the transaction; recomputing price * quantity gives a different number.

Step 2: Build the part and the whole together

GROUP BY p.category gives one row per category. In that row, SUM(t.total_amount * p.in_stock) is the in-stock revenue and SUM(t.total_amount) is all of it.

Step 3: Turn it into a percentage

Multiply the part by 100.0 and divide by the whole. The 100.0 keeps the arithmetic in decimals so nothing is lost before you decide how to trim it.

Step 4: Truncate to whole points with CAST

CAST(... AS INTEGER) drops the fraction: 97.8 becomes 97. That is the slide's rule. ROUND would push it to 98, which is a different answer, not a style choice.

Step 5: Make the order total

Sort by in_stock_revenue_pct DESC, then p.category. Dropping the fraction collapses nearby shares onto the same whole point, so without the alphabetical tiebreak those rows come back in whatever order the engine likes.


The solution

The flag weights the numerator; `CAST` trims to whole points
SELECT p.category,
    CAST(SUM(t.total_amount * p.in_stock) * 100.0 / SUM(t.total_amount) AS INTEGER) AS in_stock_revenue_pct
FROM transactions t
JOIN products p ON t.product_id = p.product_id
GROUP BY p.category
ORDER BY in_stock_revenue_pct DESC, p.category

Common follow-up questions