Canteen Menu Prices on 15 March — SQL Medium Problem
Every item on the hostel canteen's menu cost ₹10 when the menu was launched.
- Difficulty: Medium
- Topics: Subqueries, Dates
- Dialect: MySQL
- Problem: #29
Problem statement
Every item on the hostel canteen's menu cost ₹10 when the menu was launched. Since then, each price change has been logged with the date it took effect; a change stays in force until the item's next change.
For every item that appears in the log, return its price on 2025-03-15, with columns item_id and price. A change that takes effect on 2025-03-15 already applies that day; an item whose first change is later than that still costs 10. Return the rows in any order.
Tables
Table: MenuPriceChange
| Column | Type |
|---|---|
| item_id | int |
| new_price | int |
| effective_on | date |
Primary key: item_id, effective_on.
One row per price change: from effective_on onwards the item costs new_price rupees, until its next change.
Examples
Example 1
MenuPriceChange
| item_id | new_price | effective_on |
|---|---|---|
| 1 | 15 | 2025-01-10 |
| 1 | 18 | 2025-03-15 |
| 2 | 12 | 2025-02-01 |
| 2 | 14 | 2025-03-20 |
| 3 | 25 | 2025-04-01 |
| 4 | 20 | 2025-03-01 |
| 4 | 22 | 2025-02-01 |
Output
| item_id | price |
|---|---|
| 1 | 18 |
| 2 | 12 |
| 3 | 10 |
| 4 | 20 |
How to solve Canteen Menu Prices on 15 March
The price in force on a day is the new_price of the item's most recent change on or before that day. Changes after 2025-03-15 are irrelevant, and an item with no change by then still has the launch price of 10.
A correlated scalar subquery says exactly that. Start from the distinct items (so an item whose only changes are in the future still gets a row), and for each one look up its changes with effective_on <= '2025-03-15', newest first, LIMIT 1. If there is none, the scalar subquery is NULL and COALESCE(…, 10) supplies the default. The <= is what makes a change dated on the 15th count. Dates are compared as text, which works because they are all YYYY-MM-DD.
Two other plans give the same answer. A set-based one finds each item's latest qualifying date with MAX(effective_on) grouped by item, joins back to read the price (here with a row-value IN), and adds the default-price items with UNION ALL — those are the items whose earliest change is after the date. A window version ranks each item's qualifying changes with ROW_NUMBER() newest first and LEFT JOINs rank 1 onto the item list. With an index on (item_id, effective_on), each lookup in the correlated version is a short index seek.
Reference solution (MySQL)
SELECT i.item_id,
COALESCE((SELECT p.new_price
FROM MenuPriceChange p
WHERE p.item_id = i.item_id AND p.effective_on <= '2025-03-15'
ORDER BY p.effective_on DESC
LIMIT 1), 10) AS price
FROM (SELECT DISTINCT item_id FROM MenuPriceChange) iAnother way
SELECT p.item_id, p.new_price AS price
FROM MenuPriceChange p
WHERE (p.item_id, p.effective_on) IN (
SELECT item_id, MAX(effective_on) FROM MenuPriceChange
WHERE effective_on <= '2025-03-15' GROUP BY item_id
)
UNION ALL
SELECT item_id, 10 FROM MenuPriceChange GROUP BY item_id HAVING MIN(effective_on) > '2025-03-15'Another way
SELECT i.item_id, COALESCE(r.new_price, 10) AS price
FROM (SELECT DISTINCT item_id FROM MenuPriceChange) i
LEFT JOIN (
SELECT item_id, new_price, ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY effective_on DESC) AS rn
FROM MenuPriceChange WHERE effective_on <= '2025-03-15'
) r ON r.item_id = i.item_id AND r.rn = 1← Student With the Most Study Buddies · Crop Cover for Shared Amounts on Unique Plots →