Recent Price Drops
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
- L5
Problem
Every product in products carries an in_stock flag, and every sale in transactions records the product_id sold and its transaction_date. List the ID and name of each product that is in stock or sold within the last seven days, one row per product.
The price just dropped. Who noticed?
Tables
products
Columns: product_id INTEGER, product_name TEXT, in_stock INTEGER, category TEXT, price REAL, rating REAL
| 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 |
transactions
Columns: transaction_id INTEGER, product_id INTEGER, transaction_date TEXT, user_id INTEGER, quantity INTEGER, total_amount REAL
| 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 |
Expected output
| product_id | product_name |
|---|---|
| 1001 | Basic Device 1X |
| 1050 | Deluxe Bundle 2X |
| 1099 | Pro Unit 3X |
| 1148 | Ultra Tool 4X |
| 1197 | Essential Set 5X |
Worked solution and explanation
This is a set union dressed up as a freshness check. A product qualifies for one of two unrelated reasons: a flag on its own row in products, or a sale in transactions within the last seven days. Everyone spots both conditions. What separates candidates is noticing that only one of them needs transactions at all. Join first and filter on 'sold recently OR in stock' afterwards, and the inner join has already thrown away every product that never sold, which is over a hundred of the 200, in stock or not. The products that survive come back once per sale.
The trap: one join, one OR
SELECT p.product_id, p.product_name
FROM products p
JOIN transactions t ON p.product_id = t.product_id
WHERE t.transaction_date >= DATE('now', '-7 day')
OR p.in_stock = 1Contrast: the OR runs after the inner join, so a product with no row in transactions never reaches it.
Build it as two lists
Step 1: Collect the products sold in the past week
Join products to transactions on product_id and keep sales with transaction_date >= DATE('now', '-7 day'). This side genuinely needs the join: the sale date lives only in transactions. Rows with a NULL product_id match nothing and drop out, which is right.
Step 2: Collect the in-stock products straight from products
in_stock = 1 is a fact about the product row, so read it from products alone. No join is the whole point: a product with zero sales is still a candidate here.
Step 3: Merge the two lists with UNION
A product can sit in both lists, in stock and sold this week. UNION (not UNION ALL) collapses that overlap so every product appears once. Both branches select product_id, product_name in the same order, which is what lets them stack.
The solution
SELECT DISTINCT p.product_id, p.product_name
FROM products p
JOIN transactions t ON p.product_id = t.product_id
WHERE t.transaction_date >= DATE('now', '-7 day')
UNION
SELECT product_id, product_name
FROM products
WHERE in_stock = 1The join lives only in the branch that needs the sale date.
- Join, then OR
- Every row must survive the inner join to
transactions, so products with no sales are gone before theORruns, and repeat sales repeat the product. - Two branches, then UNION
- The stock branch never touches
transactions, the sales branch is narrowed to one row per product, andUNIONremoves products that qualify both ways.
Common follow-up questions
- How would you also return why each product qualified: in stock, sold this week, or both? (Tests moving from a set union to per-product flags, for example a `LEFT JOIN` from `products` to a pre-collapsed recent-sales set.)
- Some `transactions` rows have a `NULL` `product_id`. What happens to them in each branch? (Tests that the inner join drops them, which is correct: a sale with no product cannot make a product qualify.)
- If 'the last seven days' meant the store's local time rather than UTC, what changes? (Tests date versus timestamp reasoning around the `DATE('now', '-7 day')` cutoff.)