MySQL min() Function
MIN() returns the smallest value in a set: the cheapest price, the earliest date, the lowest stock. It is the mirror image of MAX() and follows the same rules.
1. Basic MIN
Table: products (6 rows)
| sku | name | category | price | stock | best_before |
|---|---|---|---|---|---|
| NUT-01 | Cashews 500g | Dry Fruits | 1890.00 | 8 | 2026-08-31 |
| NUT-02 | Almonds 500g | Dry Fruits | 699.00 | NULL | 2026-11-30 |
| OIL-01 | Groundnut Oil 1L | Oils | 250.00 | 0 | 2026-09-30 |
| OIL-02 | Olive Oil 1L | Oils | 1299.00 | 12 | 2027-06-30 |
| RIC-01 | Basmati Rice 5kg | Grains | 899.00 | 40 | 2027-01-31 |
| RIC-02 | Sona Masoori 5kg | Grains | 640.00 | 55 | 2026-12-15 |
SELECT MIN(price) AS cheapest, MIN(best_before) AS soonest_expiry, MIN(stock) AS lowest_stock FROM products;
Result: 1 row
| cheapest | soonest_expiry | lowest_stock |
|---|---|---|
| 250.00 | 2026-08-31 | 0 |
Almonds have a NULL stock, which MIN ignores. For text, MIN returns the value that sorts first:
SELECT MIN(name) AS first_alphabetically FROM products;
Result: 1 row
| first_alphabetically |
|---|
| Almonds 500g |
2. MIN with WHERE
SELECT MIN(price) AS cheapest_in_stock FROM products WHERE stock > 0;
Result: 1 row
| cheapest_in_stock |
|---|
| 640.00 |
SELECT MIN(best_before) AS next_expiry FROM products WHERE best_before >= '2026-10-01';
Result: 1 row
| next_expiry |
|---|
| 2026-11-30 |
3. MIN per Group
SELECT category, MIN(price) AS from_price, COUNT(*) AS items FROM products GROUP BY category ORDER BY from_price;
Result: 3 rows
| category | from_price | items |
|---|---|---|
| Oils | 250.00 | 2 |
| Grains | 640.00 | 2 |
| Dry Fruits | 699.00 | 2 |
4. Finding the Cheapest Product in Each Category
To return the product itself, not just the price, compare each row with its category's minimum:
SELECT p.category, p.name, p.price
FROM products p
JOIN (SELECT category, MIN(price) AS min_price FROM products GROUP BY category) m
ON m.category = p.category AND m.min_price = p.price
ORDER BY p.category;
Result: 3 rows
| category | name | price |
|---|---|---|
| Dry Fruits | Almonds 500g | 699.00 |
| Grains | Sona Masoori 5kg | 640.00 |
| Oils | Groundnut Oil 1L | 250.00 |
Or with a window function:
SELECT category, name, price
FROM (SELECT p.*, MIN(price) OVER (PARTITION BY category) AS cat_min FROM products p) t
WHERE price = cat_min
ORDER BY category;
Result: 3 rows
| category | name | price |
|---|---|---|
| Dry Fruits | Almonds 500g | 699.00 |
| Grains | Sona Masoori 5kg | 640.00 |
| Oils | Groundnut Oil 1L | 250.00 |
5. MIN and MAX Together
SELECT category,
MIN(price) AS lowest,
MAX(price) AS highest,
MAX(price) - MIN(price) AS price_range
FROM products
GROUP BY category;
Result: 3 rows
| category | lowest | highest | price_range |
|---|---|---|---|
| Dry Fruits | 699.00 | 1890.00 | 1191.00 |
| Oils | 250.00 | 1299.00 | 1049.00 |
| Grains | 640.00 | 899.00 | 259.00 |
6. MIN vs LEAST
LEAST() picks the smallest of several values in the same row, for example the lower of the list price and a promotional price:
SELECT name, price,
LEAST(price, price * 0.9, price - 100) AS best_offer
FROM products
WHERE category = 'Oils';
Result: 2 rows
| name | price | best_offer |
|---|---|---|
| Groundnut Oil 1L | 250.00 | 150.000 |
| Olive Oil 1L | 1299.00 | 1169.100 |
7. Empty Sets Return NULL
SELECT MIN(price) AS cheapest_spice, COALESCE(MIN(price), 0) AS with_default FROM products WHERE category = 'Spices';
Result: 1 row
| cheapest_spice | with_default |
|---|---|
| NULL | 0.00 |
8. Practice Exercises
Exercise 1: The earliest-expiring product that is in stock, with its name.
SELECT name, best_before FROM products WHERE stock > 0 ORDER BY best_before LIMIT 1;
Result: 1 row
| name | best_before |
|---|---|
| Cashews 500g | 2026-08-31 |
Exercise 2: Categories whose cheapest product costs more than 600.
SELECT category, MIN(price) AS from_price FROM products GROUP BY category HAVING MIN(price) > 600;
Result: 2 rows
| category | from_price |
|---|---|
| Dry Fruits | 699.00 |
| Grains | 640.00 |
9. Summary
MIN(col)returns the smallest non-NULL value; for dates the earliest, for text the first in sort order.- Combine with WHERE and GROUP BY; returns NULL on an empty set.
- Join back to a MIN sub-query or use MIN() OVER to get the full row.
- LEAST compares values within one row.