Across the Aisles

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
L4

Problem

Which of our shoppers spread their purchases under $500 across more than one product category? Working from our transactions and each product's category, list them with how many categories they bought from, most categories first.

The best customers never stay in one aisle.

Tables

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

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 13 rows
user_idcategory_count
4883
14582
13612
12642
10702

Worked solution and explanation

What this really tests

Under the marketing costume this is a per-shopper distinct count over a filtered slice, with a threshold on the count. Two decisions carry it. The $500 cut is a rule about each purchase, so it decides which rows exist before any shopper is summarized. The segment is about variety, so the number that matters is how many different categories survive the cut, not how many purchases. Count rows and a shopper whose only small buys are two orders from one aisle sails past 'more than one'. Drop the cut and every full history comes back, readmitting exactly the people the segment was built to exclude.


Building it

Step 1: Keep only purchases under $500

WHERE t.total_amount < 500 runs before grouping, which is right: the limit is a property of each purchase, not of the shopper. Once rows are grouped there is no single amount left to compare.

Step 2: Attach the category with an inner join

Join transactions to products on product_id. A purchase with a NULL product_id matches nothing and falls away: no product, no category.

Step 3: Count different categories per user

Group by t.user_id and compute COUNT(DISTINCT p.category). Repeat buys in one aisle collapse to one, so two small book orders still read as a single category.

Step 4: Threshold the count with HAVING

The count does not exist until rows are grouped, so HAVING COUNT(DISTINCT p.category) > 1 is the only place the rule can live. On this data it removes the shoppers whose small purchases all landed in one category.

Step 5: Sort widest reach first

ORDER BY category_count DESC. Shoppers tied on the count may come in any order, so no tiebreaker is owed.

The solution

Different categories per shopper among purchases under $500
SELECT t.user_id, COUNT(DISTINCT p.category) AS category_count
FROM transactions t
JOIN products p ON t.product_id = p.product_id
WHERE t.total_amount < 500
GROUP BY t.user_id
HAVING COUNT(DISTINCT p.category) > 1
ORDER BY category_count DESC
Counts purchases
HAVING COUNT(*) > 1 or COUNT(p.category) > 1 passes anyone with two small orders, even two from the same aisle, and reports purchase volume as category_count.
Counts categories
COUNT(DISTINCT p.category) in both the SELECT and the HAVING measures variety, so repeat buys count once and only real multi-category shoppers survive.

Common follow-up questions