Si buscas FIRST() y LAST() en MySQL, la respuesta corta es que no existen como funciones de agregación (sí en Microsoft Access). Lo que quieres hacer se resuelve de otras formas, según qué entiendas por «primero» y «último».
Usaré una tabla de pedidos:
pedidos (id, cliente_id, fecha, total)
1. El primer y el último de toda la tabla: ORDER BY + LIMIT
-- El más antiguo
SELECT * FROM pedidos ORDER BY fecha ASC, id ASC LIMIT 1;
-- El más reciente
SELECT * FROM pedidos ORDER BY fecha DESC, id DESC LIMIT 1;
Añade siempre un segundo criterio (id) para desempatar: sin él, dos filas con la misma fecha pueden salir en el orden que decida el motor.
2. Solo el valor: MIN y MAX
SELECT MIN(fecha) AS primero, MAX(fecha) AS ultimo FROM pedidos;
Si lo que necesitas es el valor de una columna (la fecha del primer y último pedido), esto es lo más rápido y claro. Ojo! no mezcles: SELECT MAX(fecha), total no te da el total del último pedido, sino el de una fila cualquiera (o un error, si tienes activado ONLY_FULL_GROUP_BY).
3. El primero y el último de cada grupo
El caso que más se busca: el último pedido de cada cliente. Con funciones de ventana (MySQL 8 o MariaDB 10.2 en adelante):
SELECT *
FROM (
SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY cliente_id ORDER BY fecha DESC, id DESC) AS n
FROM pedidos p
) t
WHERE n = 1;
Cambiando DESC por ASC tienes el primero de cada cliente. En versiones más antiguas, sin funciones de ventana, se usa una subconsulta:
SELECT p.*
FROM pedidos p
JOIN (SELECT cliente_id, MAX(fecha) AS fecha FROM pedidos GROUP BY cliente_id) u
ON u.cliente_id = p.cliente_id AND u.fecha = p.fecha;
Ojo: si un cliente tiene dos pedidos en la misma fecha, esta versión devuelve los dos.
4. FIRST_VALUE() y LAST_VALUE()
MySQL 8 y MariaDB sí traen estas funciones, pero son de ventana, no de agregación:
SELECT DISTINCT cliente_id,
FIRST_VALUE(total) OVER w AS primer_total,
LAST_VALUE(total) OVER w AS ultimo_total
FROM pedidos
WINDOW w AS (PARTITION BY cliente_id ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
La cláusula ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING es la que más se olvida. Sin ella, LAST_VALUE devuelve el valor de la fila actual en vez del último del grupo.
5. Mi truco favorito GROUP_CONCAT
Antes de las funciones de ventana, la forma de sacar el primer y el último valor dentro de un GROUP BY, junto a otras agregaciones, era concatenar ordenado y cortar:
SELECT cliente_id,
SUBSTRING_INDEX(GROUP_CONCAT(total ORDER BY fecha, id), ',', 1) AS primer_total,
SUBSTRING_INDEX(GROUP_CONCAT(total ORDER BY fecha DESC, id DESC), ',', 1) AS ultimo_total
FROM pedidos
GROUP BY cliente_id;
Sigue funcionando, pero devuelve texto y depende de group_concat_max_len: si el grupo es largo, la cadena se corta sin avisar. Lo cuento con más detalle en Cómo utilizar la función LAST() y FIRST() en MySQL, el post donde lo usé por primera vez.
Velocidad: los índices
Todas estas consultas ordenan, y ordenar una tabla grande sin índice es leerla entera. Para el último de toda la tabla basta un índice por (fecha, id); para el último de cada cliente, uno por (cliente_id, fecha, id). Con eso, ORDER BY … LIMIT 1 lee una sola fila del índice. Compruébalo con EXPLAIN: si aparece Using filesort en una tabla con millones de filas, falta el índice.
Cuál usar
- Un valor suelto:
MIN/MAX. - Una fila entera de toda la tabla:
ORDER BY … LIMIT 1. - Una fila por grupo:
ROW_NUMBER()en MySQL 8 o MariaDB; subconsulta conMAXen versiones antiguas. - Primer y último valor junto a otras agregaciones:
FIRST_VALUE/LAST_VALUE, oGROUP_CONCATsi no tienes funciones de ventana.
Y si buscas condicionales dentro de la consulta: IF en consultas MySQL.