FIRST_VALUE

FIRST_VALUE devuelve el valor de una expresión evaluada en la primera fila de la ventana, según el orden especificado. Es la función ideal para comparaciones de línea base: puedes mostrar junto a cada fila el primer valor de su grupo y calcular cuánto se desvía del punto de referencia.

En análisis de negocio, el "primer valor" tiene muchos significados prácticos: el primer empleado contratado, la primera venta del mes, el precio más bajo de una categoría (si ordenas ascendentemente), o el salario más alto del departamento (si ordenas descendentemente). FIRST_VALUE te permite traer esa referencia a cada fila sin subconsultas.

Sintaxis

FIRST_VALUE(expresion) OVER (
    [PARTITION BY columna1, columna2, ...]
    ORDER BY columna1 [ASC|DESC], columna2 [ASC|DESC], ...
    [especificación_de_marco]
)

La función acepta un solo argumento: la expresión cuyo primer valor dentro de la ventana quieres obtener. A diferencia de LAG y LEAD, no tiene parámetros de offset ni valor por defecto. El ORDER BY determina qué fila se considera "primera". La especificación de marco rara vez se necesita con FIRST_VALUE porque el marco por defecto (UNBOUNDED PRECEDING a CURRENT ROW) siempre incluye la primera fila de la partición.

Comportamiento básico

En su forma más simple, FIRST_VALUE devuelve el mismo valor para todas las filas de la partición, correspondiente a la primera fila según el orden:

SELECT
    nombre,
    departamento,
    salario,
    FIRST_VALUE(nombre) OVER (
        PARTITION BY departamento
        ORDER BY salario DESC
    ) AS mejor_pagado
FROM empleados;
nombredepartamentosalariomejor_pagado
Roberto FernándezMarketing72000.00Roberto Fernández
Laura DíazMarketing58000.00Roberto Fernández
Sofía MorenoMarketing45000.00Roberto Fernández
Carlos LópezVentas68000.00Carlos López
Ana GarcíaVentas62000.00Carlos López
Marta RuizVentas55000.00Carlos López
Pedro SánchezVentas48000.00Carlos López

Cada fila muestra quién tiene el salario más alto en su departamento. Como ordenamos descendentemente por salario, la primera fila de cada partición es la del empleado con mayor salario. El valor se repite en todas las filas del mismo departamento.

Comparación con el primer valor

El verdadero poder de FIRST_VALUE aparece cuando lo usas para calcular diferencias respecto al primer valor:

SELECT
    nombre,
    departamento,
    salario,
    FIRST_VALUE(salario) OVER (
        PARTITION BY departamento
        ORDER BY salario DESC
    ) AS salario_maximo,
    salario - FIRST_VALUE(salario) OVER (
        PARTITION BY departamento
        ORDER BY salario DESC
    ) AS diferencia_vs_maximo
FROM empleados;
nombredepartamentosalariosalario_maximodiferencia_vs_maximo
Roberto FernándezMarketing72000.0072000.000.00
Laura DíazMarketing58000.0072000.00-14000.00
Sofía MorenoMarketing45000.0072000.00-27000.00
Carlos LópezVentas68000.0068000.000.00
Ana GarcíaVentas62000.0068000.00-6000.00
Marta RuizVentas55000.0068000.00-13000.00

La primera fila de cada departamento tiene diferencia 0 (ella misma es el máximo). Las demás muestran cuánto les falta para alcanzar al mejor pagado de su equipo.

Caso práctico: comparación con la primera venta del mes

En análisis de ventas, comparar cada venta del día con la primera venta del mes revela cómo evoluciona el negocio desde el arranque del periodo:

SELECT
    fecha_venta,
    total,
    FIRST_VALUE(total) OVER (
        PARTITION BY YEAR(fecha_venta), MONTH(fecha_venta)
        ORDER BY fecha_venta
    ) AS primera_venta_mes,
    ROUND(
        (total - FIRST_VALUE(total) OVER (
            PARTITION BY YEAR(fecha_venta), MONTH(fecha_venta)
            ORDER BY fecha_venta
        )) / FIRST_VALUE(total) OVER (
            PARTITION BY YEAR(fecha_venta), MONTH(fecha_venta)
            ORDER BY fecha_venta
        ) * 100,
        2
    ) AS variacion_vs_primera
FROM ventas
WHERE id_cliente = 100 AND YEAR(fecha_venta) = 2024
ORDER BY fecha_venta
LIMIT 6;
fecha_ventatotalprimera_venta_mesvariacion_vs_primera
2024-01-05250.00250.000.00
2024-01-12380.00250.0052.00
2024-01-28175.00250.00-30.00
2024-02-03420.00420.000.00
2024-02-15310.00420.00-26.19
2024-02-22540.00420.0028.57

El PARTITION BY YEAR(fecha_venta), MONTH(fecha_venta) reinicia el primer valor en cada mes. En enero, la primera venta fue de 250 y las siguientes se comparan contra esa referencia. En febrero, el punto de referencia cambia a 420.

Caso práctico: primer empleado contratado por departamento

FIRST_VALUE puede extraer cualquier columna de la primera fila, no solo valores numéricos. Aquí obtenemos el nombre del empleado más antiguo de cada departamento:

SELECT
    nombre,
    departamento,
    fecha_contratacion,
    FIRST_VALUE(nombre) OVER (
        PARTITION BY departamento
        ORDER BY fecha_contratacion
    ) AS empleado_mas_antiguo,
    FIRST_VALUE(fecha_contratacion) OVER (
        PARTITION BY departamento
        ORDER BY fecha_contratacion
    ) AS fecha_primer_contrato,
    DATEDIFF(
        fecha_contratacion,
        FIRST_VALUE(fecha_contratacion) OVER (
            PARTITION BY departamento
            ORDER BY fecha_contratacion
        )
    ) AS dias_despues_del_primero
FROM empleados
ORDER BY departamento, fecha_contratacion;
nombredepartamentofecha_contratacionempleado_mas_antiguofecha_primer_contratodias_despues_del_primero
Roberto FernándezMarketing2018-03-15Roberto Fernández2018-03-150
Laura DíazMarketing2020-07-01Roberto Fernández2018-03-15839
Sofía MorenoMarketing2022-01-10Roberto Fernández2018-03-151397
Carlos LópezVentas2017-06-20Carlos López2017-06-200
Ana GarcíaVentas2019-09-05Carlos López2017-06-20808
Marta RuizVentas2021-04-12Carlos López2017-06-201392

Roberto lleva 839 días más que Laura en Marketing. Carlos fue el primero en Ventas y Ana llegó 808 días después. Este tipo de análisis es útil para calcular antigüedad relativa dentro de cada equipo.

Caso práctico: precio de referencia por categoría

En e-commerce, comparar cada producto con el más barato (o más caro) de su categoría es un análisis frecuente para estrategias de precios:

SELECT
    nombre,
    categoria,
    precio,
    FIRST_VALUE(precio) OVER (
        PARTITION BY categoria
        ORDER BY precio
    ) AS precio_minimo_cat,
    FIRST_VALUE(nombre) OVER (
        PARTITION BY categoria
        ORDER BY precio
    ) AS producto_mas_barato,
    ROUND(precio / FIRST_VALUE(precio) OVER (
        PARTITION BY categoria
        ORDER BY precio
    ), 2) AS factor_vs_minimo
FROM productos
WHERE activo = 1
ORDER BY categoria, precio;
nombrecategoriaprecioprecio_minimo_catproducto_mas_baratofactor_vs_minimo
Ratón inalámbricoElectrónica29.9029.90Ratón inalámbrico1.00
Teclado mecánico RGBElectrónica129.0029.90Ratón inalámbrico4.31
Monitor 27" 4KElectrónica349.0029.90Ratón inalámbrico11.67
Camiseta algodón orgánicoRopa25.0025.00Camiseta algodón orgánico1.00
Pantalón chino slimRopa49.9025.00Camiseta algodón orgánico2.00
Abrigo lana italianoRopa245.0025.00Camiseta algodón orgánico9.80

El factor 11.67 del monitor indica que cuesta casi 12 veces más que el producto más barato de su categoría. Este análisis ayuda a entender la dispersión de precios dentro de cada categoría.

Manejo de NULL

Si la primera fila de la partición tiene NULL en la columna referenciada, FIRST_VALUE devuelve NULL para todas las filas de esa partición:

SELECT
    nombre,
    comision,
    FIRST_VALUE(comision) OVER (ORDER BY comision) AS primera_comision
FROM empleados;
nombrecomisionprimera_comision
Pedro SánchezNULLNULL
Elena TorresNULLNULL
David Moreno3000.00NULL
Roberto Fernández5000.00NULL

Como los NULL se ordenan primero en orden ascendente, el primer valor es NULL, y eso se propaga a todas las filas. Si quieres que el primer valor sea el primer valor no nulo, filtra los NULL o usa un orden que los coloque al final:

FIRST_VALUE(comision) OVER (
    ORDER BY ISNULL(comision), comision
)

Con ISNULL(comision), los NULL se ordenan después de todos los valores no nulos, haciendo que el primer valor sea 3000.

Combinación con otras funciones

FIRST_VALUE se combina con funciones de agregación de ventana para crear análisis multidimensionales donde cada fila muestra tanto su posición individual como la referencia del grupo:

SELECT
    nombre,
    departamento,
    salario,
    FIRST_VALUE(salario) OVER w AS maximo_depto,
    AVG(salario) OVER w AS promedio_depto,
    ROUND(salario / FIRST_VALUE(salario) OVER w * 100, 1) AS pct_del_maximo,
    ROUND(salario / AVG(salario) OVER w * 100, 1) AS pct_del_promedio
FROM empleados
WINDOW w AS (PARTITION BY departamento ORDER BY salario DESC)
ORDER BY departamento, salario DESC;
nombredepartamentosalariomaximo_deptopromedio_deptopct_del_maximopct_del_promedio
Roberto FernándezMarketing72000.0072000.0072000.00100.0100.0
Laura DíazMarketing58000.0072000.0065000.0080.689.2
Sofía MorenoMarketing45000.0072000.0058333.3362.577.1

El uso de la cláusula WINDOW evita repetir la definición de ventana en cada función. Cada fila muestra qué porcentaje alcanza respecto al máximo y respecto al promedio de su departamento.

Practica con FIRST_VALUE

Usa el editor para obtener el primer valor de cada particion:

Simulador SQL
Ctrl+Enter para ejecutar

En el siguiente artículo veremos LAST_VALUE para obtener el último valor de la ventana.

Escrito por Eduardo Lázaro