Calculate daily sales and accumulated total

Situation

We need a daily sales series and, next to each day, the amount accumulated since the beginning of the period. The first calculation reduces order lines to one row per day; the second uses those rows without reducing them again.

First approach

Trying to directly apply a window function on items makes it difficult to distinguish the day’s total from the accumulated one. First we will build the daily level and then calculate the window.

A window function calculates a value over related rows without combining them into a single row. In SUM(...) OVER (...), OVER defines those rows; frame ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW starts at the first sorted row and ends at the row being read.

Before forming the daily series, CONVERT(DATE, pe.FechaPedido) preserves the date and removes the time part so that all orders on the same day belong to the same group.

Final query

DECLARE @Desde DATE = '20260301';
DECLARE @Hasta DATE = '20260401';

WITH VentasDiarias AS
(
    SELECT
        CONVERT(DATE, pe.FechaPedido) AS Fecha,
        SUM
        (
            d.Cantidad * d.PrecioUnitario
            * (1 - d.Descuento / 100.0)
        ) AS VentaDia
    FROM dbo.Pedido AS pe
    INNER JOIN dbo.PedidoDetalle AS d
        ON d.IdPedido = pe.IdPedido
    WHERE pe.Estado IN ('Confirmado', 'Enviado')
      AND pe.FechaPedido >= @Desde
      AND pe.FechaPedido <  @Hasta
    GROUP BY CONVERT(DATE, pe.FechaPedido)
)
SELECT
    v.Fecha,
    v.VentaDia,
    SUM(v.VentaDia) OVER
    (
        ORDER BY v.Fecha
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS VentaAcumulada
FROM VentasDiarias AS v
ORDER BY v.Fecha;

How it works

The CTE, a named query available to the following statement, filters the period with an exclusive upper boundary and groups by date without time. VentaDia represents one row for each day with accepted orders.

The second sum uses OVER: it keeps those rows and accumulates from the first to the current one. Explicitly writing the ROWS framework makes it clear which rows are involved and avoids relying on implicit tie behavior.

Expected result

Date SaleDay AccumulatedSale
2026-03-01 728.00 728.00
2026-03-04 719.10 1447.10
2026-03-05 624.15 2071.25

Days without sales do not appear because there is no row to group.

Verified in SSMS

The grid shows how each daily sale contributes to the running total on the next row.

Actual SSMS result for daily and running sales totals

Variation: show days at zero

For a continuous chart, we need a calendar table—a table with one row per date and its attributes—or a controlled date sequence, plus a LEFT JOIN to VentasDiarias. We then turn absence into zero with COALESCE.

We can also add PARTITION BY to reset the accumulation by month, branch or currency.

Limits and cautions

The running total is meaningful only when the order is stable and the figures share the same unit. We do not add different currencies or mix canceled orders with accepted sales.

Converting the date column inside the filter can make efficient use of an index harder. An index organizes values to make rows easier to access. That is why WHERE compares the original column with boundaries and reserves conversion for grouping and presentation.

Path index

Applied cases

10 articles in Applied cases
  1. 01Query customers with confirmed orders
  2. 02Find customers who do not have orders yet
  3. 03Show the first available phone number for each customer
  4. 04Calculate total sales by customer and filter the highest totals
  5. 05Insert an order and its line items safely
  6. 06Update category prices with prior validation
  7. 07Combine current and historical orders
  8. 08Return the latest order for each customer
  9. 09Calculate daily sales and a running total
  10. 10Build paginated search with optional filters