Tag: Window Functions

  • Window Functions in SQL Server: ROW_NUMBER, RANK, DENSE_RANK, and Running Totals

    Window Functions in SQL Server: ROW_NUMBER, RANK, DENSE_RANK, and Running Totals

    The single most important thing to understand about window functions: unlike GROUP BY (Fundamentals Chapter 4), every original row stays in the result, enriched with a calculated value alongside it, rather than collapsed into one row per group. This is the tool for “rank each row within its group” or “running total as of this row” — questions GROUP BY structurally cannot answer, since it always reduces row count.

    Window Functions: Every Row Stays(unlike GROUP BY, which collapses rows)FRAME (current frame)$100$250$400$150$300row 1row 2row 3 (current)row 4 (not yet)row 5 (not yet)running total — SUM() OVER (… UNBOUNDED PRECEDING)1003507509001200

    Ranking Within Groups

    SELECT salesperson, region, amount, sale_date,
        ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) AS row_num,
        RANK()       OVER (PARTITION BY region ORDER BY amount DESC) AS rank_num,
        DENSE_RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS dense_rank_num
    FROM dbo.Sale;

    PARTITION BY is doing the conceptual work GROUP BY would do — splitting rows into groups — but instead of collapsing each group into one row, it just resets the ranking/calculation at each group boundary while keeping every row visible.

    Running Totals

    SELECT salesperson, region, sale_date, amount,
        SUM(amount) OVER (PARTITION BY region ORDER BY sale_date
                           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
    FROM dbo.Sale;

    The ROWS BETWEEN ... AND CURRENT ROW clause is called a frame — it defines exactly which rows, relative to the current one, get included in the calculation. “Unbounded preceding to current row” means “every row from the start of this partition up through this one,” which is precisely what a running total means.

    -- A different frame answers a different question: a 3-row moving average
    SELECT salesperson, region, sale_date, amount,
        AVG(amount) OVER (PARTITION BY region ORDER BY sale_date
                           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3
    FROM dbo.Sale;

    How Ties Are Handled — The Difference That Actually Matters

    Function Behavior on ties
    ROW_NUMBER() Always unique, arbitrarily breaks ties (1,2,3,4…)
    RANK() Ties share a rank, next rank skips (1,1,3,4…)
    DENSE_RANK() Ties share a rank, next rank doesn’t skip (1,1,2,3…)
    Common mistake: Using ROW_NUMBER() to find “the top 3 salespeople,” which silently discards a genuine 3-way tie for 3rd place down to one arbitrary row. If ties should all be included, RANK() <= 3 is the correct tool — it can return more than 3 rows when there’s a tie at the boundary, which is usually exactly what “top 3” should mean in a real report.

    Comparing to the Previous Row: LAG and LEAD

    SELECT salesperson, sale_date, amount,
        LAG(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) AS previous_sale,
        amount - LAG(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) AS change_from_last
    FROM dbo.Sale;

    LAG reaches backward, LEAD reaches forward — both eliminate what used to require an awkward self-join to compare a row against its neighbor, exactly the SELF JOIN pattern from Fundamentals Chapter 5, now solved far more cleanly.

    GROUP BY vs Window Functions

    GROUP BY Fewer rows — one per group Window Function Every row kept, enriched

    Practice tip: Write a query answering “for each sale, show what percentage it represents of its region’s total” — this needs both a window SUM (the region total, spread across every row) and simple division against each row’s own amount. It’s a genuinely common real-world request that GROUP BY alone cannot answer in a single query.

    Enjoyed this?

    Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.