Contenido de esta guía
Respuesta rápida
FIRST_VALUE(expresión) devuelve el valor de la primera fila del marco de ventana y LAST_VALUE(expresión) el de la última. Ambas funciones se usan con OVER (...) y conservan las filas de detalle.
FIRST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
LAST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)El PARTITION BY define el grupo independiente, ORDER BY decide qué significa «primera» y «última» y el frame determina qué filas están disponibles para la función en cada fila.
Modelo mental: no buscan una fila, leen un valor dentro de la ventana
Estas funciones no eliminan filas ni sustituyen a MIN() o MAX(). La pregunta no es «¿cuál es la fecha menor?», sino «después de ordenar las filas de cada cliente, ¿qué valor tiene la primera o la última fila de ese recorrido?».
Eso permite mostrar, junto a cada pedido, la primera y la última fecha de compra del mismo cliente. El detalle permanece visible porque las funciones de ventana calculan un valor por fila.
Incluye un desempate estable. Si dos filas tienen la misma fecha, añade una clave como id al ORDER BY. Sin un orden total, «primera» o «última» puede no identificar de forma determinista la misma fila.
Ejemplo comprobado: primera y última compra por cliente
SELECT
id,
cliente_id,
fecha,
FIRST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS primera_fecha,
LAST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ultima_fecha
FROM pedidos
WHERE cliente_id IN (1, 3)
ORDER BY cliente_id, fecha, id;| id | cliente_id | fecha | primera_fecha | ultima_fecha |
|---|---|---|---|---|
| 101 | 1 | 2025-05-02 | 2025-05-02 | 2025-06-01 |
| 103 | 1 | 2025-05-10 | 2025-05-02 | 2025-06-01 |
| 111 | 1 | 2025-06-01 | 2025-05-02 | 2025-06-01 |
| 104 | 3 | 2025-05-11 | 2025-05-11 | 2025-06-12 |
| 107 | 3 | 2025-05-20 | 2025-05-11 | 2025-06-12 |
| 116 | 3 | 2025-06-12 | 2025-05-11 | 2025-06-12 |
Las seis filas siguen en el resultado. Lo que se repite dentro de cada cliente son los valores calculados de la primera y la última fecha.
El detalle que suele romper LAST_VALUE: el marco de ventana
Con un ORDER BY, varios motores usan de forma predeterminada un marco que llega hasta la fila actual o sus pares. En ese caso, LAST_VALUE() puede devolver el valor de la fila actual en vez del último valor de toda la partición.
Suponer que LAST_VALUE siempre mira hasta el final
LAST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
)Si necesitas el último valor de toda la partición, declara el marco completo de forma explícita con ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
Cuándo usar estas funciones
- Primera o última fecha: inicio y fin de una secuencia por cliente, empleado o producto.
- Primer o último estado: cuando el orden temporal define una evolución y quieres conservar cada evento.
- Valor de referencia: comparar cada fila con el valor inicial o final del mismo grupo.
Si solo necesitas una fila por grupo, una agregación o un patrón Top N por grupo puede ser más directo. Si necesitas mantener todas las filas, las funciones de ventana son la herramienta natural.
Compatibilidad entre motores
PostgreSQL, SQLite, MySQL 8.4 y SQL Server ofrecen FIRST_VALUE y LAST_VALUE. La sintaxis del marco se parece, pero no conviene asumir que las opciones adicionales son idénticas. SQL Server 2022 y posteriores, por ejemplo, documentan IGNORE NULLS y RESPECT NULLS para estas funciones.
La parte más portable de la guía es la idea de PARTITION BY + ORDER BY + un frame explícito. Si necesitas ignorar NULL o una variante avanzada, confirma primero la versión y el motor.
Práctica: conserva cada pedido y muestra el total inicial y final
Compara cada pedido con los extremos del cliente
Para los clientes 1 y 3, devuelve id, cliente_id, fecha, total, el total del primer pedido y el total del último pedido. Ordena cronológicamente y usa id como desempate.
cliente_id.fecha, id.UNBOUNDED FOLLOWING.Solución razonada
SELECT
id,
cliente_id,
fecha,
total,
FIRST_VALUE(total) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS total_inicial,
LAST_VALUE(total) OVER (
PARTITION BY cliente_id
ORDER BY fecha, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS total_final
FROM pedidos
WHERE cliente_id IN (1, 3)
ORDER BY cliente_id, fecha, id;Las dos funciones comparten la misma ventana. El frame completo hace que el extremo final sea el mismo para todas las filas de cada cliente.
Qué debes recordar
FIRST_VALUEyLAST_VALUEleen extremos definidos por el orden de una ventana.- Conservan las filas de detalle.
LAST_VALUEdepende especialmente del frame; declara el marco completo cuando buscas el último valor de toda la partición.- Añade un desempate estable al
ORDER BY.
Conceptos relacionados
Fuentes técnicas consultadas
Las referencias oficiales se usaron para verificar semántica, marcos y compatibilidad.