MAX
La función MAX devuelve el valor más grande de un conjunto de valores. Es la contraparte de MIN y comparte muchas de sus características: funciona con números, fechas, cadenas de texto e ignora los valores NULL. La usarás cuando necesites encontrar el precio más alto, la fecha más reciente, el pedido más grande o el último registro cronológicamente.
Sintaxis
MAX(expresion)MAX acepta una columna o cualquier expresión que devuelva un valor comparable. Al igual que MIN, funciona con cualquier tipo de dato que MySQL pueda ordenar. Sin GROUP BY, devuelve el máximo de toda la tabla. Con GROUP BY, devuelve el máximo de cada grupo.
Comportamiento básico
Para encontrar el producto más caro del catálogo:
SELECT
MAX(precio) AS precio_maximo,
MIN(precio) AS precio_minimo,
ROUND(AVG(precio), 2) AS precio_medio
FROM productos;| precio_maximo | precio_minimo | precio_medio |
|---|---|---|
| 1899.99 | 9.99 | 384.52 |
El producto más caro cuesta 1899.99 euros. Mostrar el mínimo y la media junto al máximo da contexto: el máximo está muy por encima de la media, lo que indica que hay unos pocos productos de gama muy alta.
MAX con fechas devuelve la fecha más reciente:
SELECT
MAX(fecha_pedido) AS ultimo_pedido,
MAX(fecha_registro) AS ultimo_cliente_registrado
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id;| ultimo_pedido | ultimo_cliente_registrado |
|---|---|
| 2026-02-10 | 2026-01-28 |
Este patrón es muy útil para saber cuándo ocurrió la última actividad en el sistema.
Caso práctico: precio máximo por categoría
Con GROUP BY puedes encontrar el producto más caro de cada categoría:
SELECT
c.nombre AS categoria,
MAX(p.precio) AS precio_maximo,
ROUND(AVG(p.precio), 2) AS precio_medio,
ROUND((MAX(p.precio) - AVG(p.precio)) / AVG(p.precio) * 100, 1) AS pct_sobre_media
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
ORDER BY precio_maximo DESC;| categoria | precio_maximo | precio_medio | pct_sobre_media |
|---|---|---|---|
| Portátiles | 1899.99 | 1245.50 | 52.5 |
| Smartphones | 1299.99 | 785.42 | 65.5 |
| Deportes | 449.99 | 189.99 | 136.8 |
| Hogar | 299.99 | 124.80 | 140.4 |
| Ropa mujer | 149.99 | 64.99 | 130.8 |
| Ropa hombre | 129.99 | 59.99 | 116.7 |
| Accesorios electrónicos | 89.99 | 42.85 | 110.0 |
| Libros | 29.99 | 22.50 | 33.3 |
La columna pct_sobre_media muestra cuánto supera el producto más caro a la media de su categoría. Hogar y Deportes tienen los porcentajes más altos, lo que indica que su producto estrella está muy por encima del resto.
Caso práctico: encontrar la fila con el valor máximo
Al igual que con MIN, un problema frecuente es obtener la fila completa que contiene el valor máximo. Por ejemplo, encontrar el pedido más reciente de cada cliente:
SELECT
cl.nombre,
cl.apellidos,
p.id AS pedido_id,
p.fecha_pedido,
p.total
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
WHERE p.fecha_pedido = (
SELECT MAX(p2.fecha_pedido)
FROM pedidos p2
WHERE p2.cliente_id = p.cliente_id
)
ORDER BY p.fecha_pedido DESC
LIMIT 5;| nombre | apellidos | pedido_id | fecha_pedido | total |
|---|---|---|---|---|
| María | García López | 348 | 2026-02-10 | 856.99 |
| Carlos | Rodríguez Martín | 345 | 2026-02-08 | 1245.00 |
| Laura | Fernández Díaz | 342 | 2026-02-05 | 432.50 |
| Ana | Martínez Ruiz | 340 | 2026-02-03 | 678.90 |
| Pedro | Sánchez Gómez | 337 | 2026-01-30 | 1589.99 |
La subconsulta correlacionada encuentra la fecha máxima de pedido para cada cliente, y la consulta exterior recupera la fila completa correspondiente. Alternativamente, puedes usar ROW_NUMBER:
SELECT nombre, apellidos, pedido_id, fecha_pedido, total
FROM (
SELECT
cl.nombre,
cl.apellidos,
p.id AS pedido_id,
p.fecha_pedido,
p.total,
ROW_NUMBER() OVER (
PARTITION BY cl.id ORDER BY p.fecha_pedido DESC
) AS rn
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
) ranked
WHERE rn = 1
ORDER BY fecha_pedido DESC
LIMIT 5;Caso práctico: MAX con fechas para análisis temporal
MAX con fechas es especialmente útil para calcular métricas de recencia. Veamos cuántos días han pasado desde el último pedido de cada cliente:
SELECT
cl.nombre,
cl.apellidos,
MAX(p.fecha_pedido) AS ultimo_pedido,
DATEDIFF(CURDATE(), MAX(p.fecha_pedido)) AS dias_sin_comprar
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
WHERE p.estado = 'completado'
GROUP BY cl.id, cl.nombre, cl.apellidos
HAVING DATEDIFF(CURDATE(), MAX(p.fecha_pedido)) > 90
ORDER BY dias_sin_comprar DESC
LIMIT 5;| nombre | apellidos | ultimo_pedido | dias_sin_comprar |
|---|---|---|---|
| Roberto | Muñoz Vega | 2025-06-12 | 247 |
| Beatriz | Jiménez Soto | 2025-07-25 | 204 |
| Fernando | Ruiz Castillo | 2025-08-10 | 188 |
| Susana | Morales García | 2025-09-01 | 166 |
| Andrés | Torres Luna | 2025-10-15 | 122 |
Esta consulta identifica a los clientes inactivos (más de 90 días sin comprar), ordenados por los que llevan más tiempo sin actividad. Es la base de muchas estrategias de reactivación de clientes.
Caso práctico: MAX con cadenas de texto
MAX con cadenas devuelve el último valor en orden alfabético:
SELECT
c.nombre AS categoria,
MIN(p.nombre) AS primer_producto_alfa,
MAX(p.nombre) AS ultimo_producto_alfa
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
ORDER BY c.nombre;| categoria | primer_producto_alfa | ultimo_producto_alfa |
|---|---|---|
| Accesorios electrónicos | Cable USB-C a Lightning | Soporte portátil aluminio |
| Hogar | Juego de sábanas algodón | Vela aromática lavanda |
| Smartphones | Google Pixel 8 | Xiaomi Redmi Note 13 |
El orden depende de la intercalación de la columna. Con utf8mb4_general_ci (la predeterminada en muchas configuraciones), las mayúsculas y minúsculas se tratan como iguales.
Manejo de NULL
MAX ignora los valores NULL. Si todas las filas de un grupo tienen NULL, el resultado es NULL:
SELECT
MAX(descuento) AS mayor_descuento,
COALESCE(MAX(descuento), 0) AS mayor_o_cero
FROM productos
WHERE categoria_id = 5;| mayor_descuento | mayor_o_cero |
|---|---|
| NULL | 0 |
Si ningún libro tiene descuento, MAX devuelve NULL. COALESCE lo convierte en cero para evitar problemas en la aplicación.
Combinación con otras funciones
MAX se usa frecuentemente en HAVING para filtrar grupos según su valor máximo:
SELECT
c.nombre AS categoria,
MAX(p.precio) AS precio_maximo
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
HAVING MAX(p.precio) > 500
ORDER BY precio_maximo DESC;| categoria | precio_maximo |
|---|---|
| Portátiles | 1899.99 |
| Smartphones | 1299.99 |
Solo Portátiles y Smartphones tienen productos que superan los 500 euros.
Otra combinación útil es con subconsultas para encontrar el máximo global y comparar:
SELECT
nombre,
precio,
ROUND(precio / (SELECT MAX(precio) FROM productos) * 100, 1) AS pct_del_maximo
FROM productos
ORDER BY precio DESC
LIMIT 5;| nombre | precio | pct_del_maximo |
|---|---|---|
| ASUS ROG Zephyrus | 1899.99 | 100.0 |
| Lenovo ThinkPad X1 | 1549.00 | 81.5 |
| MacBook Air M3 | 1399.00 | 73.6 |
| iPhone 15 Pro | 1299.99 | 68.4 |
| Dell XPS 15 | 1249.00 | 65.7 |
Cada producto se muestra como un porcentaje del precio más alto, lo que da una perspectiva relativa de su posición en el catálogo.
Practica con MAX
Usa el editor para encontrar valores máximos:
En el siguiente artículo veremos GROUP_CONCAT para concatenar valores agrupados.
Escrito por Eduardo Lázaro
