OVER and PARTITION BY: calculate without losing detail

An aggregation with GROUP BY reduces multiple rows to a summary. That works when we only need the total, but not when we want to keep each item and show the total of its order next to it.

Window functions calculate over related rows without reducing them to a single row. The window is the set of rows that participates in the calculation for the current row. The OVER clause defines it, and PARTITION BY divides it into independent groups.

The idea

A window function returns a result for each row. You can calculate a total, average, position, or cumulative using other rows as context.

PARTITION BY restarts the calculation when the group changes. If we omit it, the window can cover the entire result.

Syntax

Funcion() OVER
(
    PARTITION BY ColumnaDeGrupo
    ORDER BY ColumnaDeOrden
)

Not all functions require both parts. A total per group can use only PARTITION BY; an accumulation also needs an order.

Example: order total in each item

SELECT
    pd.IdPedido,
    pd.NumeroPartida,
    pd.IdProducto,
    pd.Cantidad * pd.PrecioUnitario AS ImportePartida,
    SUM(pd.Cantidad * pd.PrecioUnitario) OVER
    (
        PARTITION BY pd.IdPedido
    ) AS ImportePedido
FROM dbo.PedidoDetalle AS pd
ORDER BY
    pd.IdPedido,
    pd.NumeroPartida;
OrderId ItemNumber ItemAmount OrderAmount
1 1 349.00 728.00
1 2 379.00 728.00
2 1 799.00 799.00

The two items from order 1 remain visible. The total of 728 is calculated over your partition and appears next to each one.

Compare a row to the average of its group

SELECT
    p.IdProducto,
    p.IdCategoria,
    p.Nombre,
    p.Precio,
    AVG(p.Precio) OVER
    (
        PARTITION BY p.IdCategoria
    ) AS PrecioPromedioCategoria
FROM dbo.Producto AS p
WHERE p.Activo = 1;

We can keep the individual price and at the same time look at the category average.

Build a rollup

The order becomes part of the calculation when each row must incorporate the previous ones:

SELECT
    m.IdProducto,
    m.FechaMovimiento,
    m.IdMovimiento,
    m.TipoMovimiento,
    m.Cantidad,
    SUM
    (
        CASE
            WHEN m.TipoMovimiento = 'E' THEN m.Cantidad
            ELSE -m.Cantidad
        END
    ) OVER
    (
        PARTITION BY m.IdProducto
        ORDER BY m.FechaMovimiento, m.IdMovimiento
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS ExistenciaCalculada
FROM dbo.MovimientoInventario AS m
ORDER BY
    m.IdProducto,
    m.FechaMovimiento,
    m.IdMovimiento;

The ROWS specification defines the frame: the portion of the window included in the calculation for the current row. Here it advances one row at a time. The identifier breaks ties between movements with the same date.

What should be checked

PARTITION BY groups for calculation, but does not sort the output. If the final order matters, we still need an outer ORDER BY.

We must also distinguish the entire window from the row frame. When we add ORDER BY inside OVER, SQL Server applies a default frame that may not represent the running total we expect, especially when ties exist. It is preferable to write ROWS explicitly.