SQL Window Functions Explained

·Updated Apr 4, 2026·
sqldatabasequery-languagewindow-functionsanalyticssql tutorial
·

Level: intermediate · ~20 min read · Intent: informational

Audience: backend developers, data analysts, data engineers, technical teams, analytics engineers, software engineers

Prerequisites

  • basic familiarity with databases
  • basic understanding of SELECT, GROUP BY, and ORDER BY

Key takeaways

  • SQL window functions let you calculate values across related rows without collapsing the result set, which makes them ideal for ranking, running totals, moving averages, and per-row comparisons.
  • The most important parts of a window function are the OVER clause, PARTITION BY, and ORDER BY, because they define which rows belong together and in what sequence the calculation should happen.

FAQ

What is a window function in SQL?
A window function is a SQL function that performs a calculation across a set of related rows while still returning one result row for each original row.
What is the difference between GROUP BY and window functions?
GROUP BY collapses multiple rows into one row per group, while window functions keep the original rows and add calculated values alongside them.
What does OVER mean in SQL window functions?
The OVER clause defines the window of rows the function should look at, including optional PARTITION BY and ORDER BY rules.
When should I use window functions in SQL?
Use window functions when you need running totals, rankings, moving averages, previous or next row comparisons, or per-group calculations without losing row-level detail.
0

SQL window functions are one of the most useful advanced SQL features because they let you calculate across multiple related rows without losing the original row detail.

That is what makes them so powerful.

A normal aggregate query can tell you:

  • average salary by department
  • total sales by month
  • number of orders per customer

Those are useful results, but they collapse the original rows.

A window function can do something different:

  • show each employee row and the average salary of that employee’s department
  • show each order row and the running total up to that order
  • show each sale row and its rank within the month
  • show each record and the previous or next value next to it

That is why window functions matter so much.

They let you keep the row-level detail while still calculating over groups, sequences, and related rows.

This guide explains SQL window functions clearly, including:

  • what they are
  • how the OVER clause works
  • how PARTITION BY changes the window
  • how ORDER BY changes the sequence
  • the most useful window functions
  • common business use cases
  • and the mistakes that make window queries confusing or slow

Why window functions matter

A lot of SQL problems are really not just about:

  • filtering
  • grouping
  • or joining

They are about comparing each row to:

  • other rows in the same group
  • previous rows in a timeline
  • next rows in a sequence
  • or the whole dataset without collapsing the result

Examples:

  • what is each employee’s salary rank in their department?
  • what is the running total of orders per customer?
  • how does today’s value compare with yesterday’s?
  • what is the average order value in the same region?
  • what is the first order per user?
  • what is the latest event per device?
  • what percentage of total revenue did this row contribute?

These are the kinds of problems window functions solve beautifully.

Without window functions, many of these tasks would require:

  • complex self-joins
  • nested subqueries
  • or multiple stages of query logic

That is why window functions are such a big step forward in practical SQL.

The most important rule

Before anything else, remember this:

Window functions calculate across related rows without collapsing the result set.

That is the single most important idea in this topic.

If you understand that clearly, a lot of the confusion disappears.

A GROUP BY query says:

  • reduce many rows into one row per group

A window function says:

  • keep all the original rows
  • but calculate something across a defined set of related rows

That is the difference that matters most.

What a window function is

A window function is a function that performs a calculation over a group of rows called a window, while still returning one output row for each input row.

That means the function can look at:

  • neighboring rows
  • grouped rows
  • earlier rows
  • later rows
  • or the full partition

but it does not destroy the row-by-row structure.

This is why window functions are often also called:

  • analytic functions
  • analytical functions

because they are extremely useful in reporting and analysis.

The OVER clause is the heart of window functions

Every window function is centered around the OVER clause.

Example:

SUM(total_amount) OVER ()

The OVER() part tells SQL:

  • this is not a normal aggregate
  • treat it as a window calculation

Without OVER, SUM(total_amount) would usually collapse rows in a grouped query.

With OVER, it becomes a window function.

That is why the OVER clause is the foundation of this whole topic.

Basic syntax pattern

A common window function pattern looks like this:

FUNCTION_NAME(expression) OVER (
    PARTITION BY ...
    ORDER BY ...
)

Where:

  • FUNCTION_NAME might be SUM, AVG, ROW_NUMBER, RANK, LAG, LEAD, and so on
  • PARTITION BY splits rows into groups
  • ORDER BY defines sequence inside the window

Not every window function needs every part. But this is the main pattern to understand.

What PARTITION BY does

PARTITION BY divides the result set into separate groups, and the window function runs independently inside each group.

Example:

AVG(salary) OVER (PARTITION BY department)

This means:

  • for each employee row
  • calculate the average salary within that employee’s department

Each department is its own partition.

A useful way to think about it is:

PARTITION BY tells SQL which rows belong together for the window calculation.

That is one of the most important concepts in window functions.

What ORDER BY does inside a window

ORDER BY inside the window defines the sequence in which rows are considered.

This matters for functions like:

  • ROW_NUMBER
  • RANK
  • LAG
  • LEAD
  • running totals
  • moving averages

Example:

SUM(total_amount) OVER (
    PARTITION BY customer_id
    ORDER BY order_date
)

This means:

  • within each customer
  • process rows in order of order date
  • so the running sum follows time order

A useful way to think about it is:

ORDER BY tells the window function what “before” and “after” mean.

That is why it is so important in sequence-based calculations.

Window functions versus GROUP BY

This is one of the most important distinctions.

Suppose you write:

SELECT
    department,
    AVG(salary)
FROM employees
GROUP BY department;

This gives you:

  • one row per department

Now compare that with:

SELECT
    employee_name,
    department,
    salary,
    AVG(salary) OVER (PARTITION BY department) AS department_avg_salary
FROM employees;

This gives you:

  • one row per employee
  • plus the department average repeated next to each employee row

That is the key difference.

GROUP BY

Collapses rows.

Window functions

Keep rows and add analytical context.

This is why window functions are so useful for detailed reporting.

A simple example dataset

Suppose you have this sales table:

sale_id sales_rep region sale_date amount
1 Alice North 2026-04-01 100
2 Alice North 2026-04-02 200
3 Bob North 2026-04-01 150
4 Cara South 2026-04-01 300
5 Cara South 2026-04-02 100

This kind of table is perfect for window function examples because it has:

  • grouped data
  • ordered data
  • and row-level detail

Example: total revenue across all rows

SELECT
    sale_id,
    sales_rep,
    region,
    amount,
    SUM(amount) OVER () AS total_revenue
FROM sales;

This means:

  • calculate the total revenue across the whole result set
  • but keep every original row

So each row gets the same overall total next to it.

This is useful for:

  • percentages of total
  • global benchmarks
  • and contextual analysis

Example: average revenue by region

SELECT
    sale_id,
    sales_rep,
    region,
    amount,
    AVG(amount) OVER (PARTITION BY region) AS region_avg_amount
FROM sales;

This means:

  • calculate the average amount within each region
  • but keep one row per sale

This is a very common analytical pattern:

  • compare each row to its group average

Example: running total

One of the most famous window function use cases is the running total.

SELECT
    sale_id,
    sales_rep,
    sale_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS running_total
FROM sales;

This means:

  • within each sales rep
  • order rows by sale date
  • then keep a cumulative sum as the rows progress

This is extremely useful for:

  • revenue tracking
  • account balances
  • cumulative users
  • progressive totals
  • and trend analysis

Why running totals matter so much

Running totals are valuable because they show:

  • how a value builds over time

That is useful in:

  • finance
  • ecommerce
  • operations
  • customer analytics
  • and time-series reporting

Without window functions, running totals were often much more awkward to calculate. With them, the logic becomes much clearer.

This is one of the best examples of why window functions are so powerful.

Example: row numbering

ROW_NUMBER() is a very common window function.

Example:

SELECT
    sale_id,
    sales_rep,
    amount,
    ROW_NUMBER() OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS sale_sequence
FROM sales;

This means:

  • within each sales rep
  • assign a sequence number to each sale in date order

This is useful for:

  • first sale
  • second sale
  • latest row per entity
  • deduplication
  • and event ordering

Example: ranking

Ranking functions are another major use case.

Example:

SELECT
    sales_rep,
    region,
    amount,
    RANK() OVER (
        PARTITION BY region
        ORDER BY amount DESC
    ) AS region_rank
FROM sales;

This ranks each sale by amount inside its region.

This is useful for:

  • leaderboards
  • top performers
  • top sales per region
  • competition-style ranking
  • and selecting top-N rows per group

Ranking functions are some of the most common window functions used in reporting.

Example: previous row with LAG

LAG() lets you look backward.

Example:

SELECT
    sales_rep,
    sale_date,
    amount,
    LAG(amount) OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS previous_amount
FROM sales;

This means:

  • for each sale
  • show the amount from the previous sale for the same rep

This is useful for:

  • change analysis
  • growth comparisons
  • sequence validation
  • and event-to-event tracking

Example: next row with LEAD

LEAD() is the forward-looking counterpart.

Example:

SELECT
    sales_rep,
    sale_date,
    amount,
    LEAD(amount) OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS next_amount
FROM sales;

This means:

  • for each sale
  • show the next sale amount for the same rep

This is useful for:

  • forecasting-style comparisons
  • journey steps
  • event sequences
  • and process flow analysis

Why LAG and LEAD are so useful

Before window functions, comparing a row to the previous or next row often required:

  • self-joins
  • subqueries
  • or very awkward logic

LAG and LEAD make that much easier.

They are extremely useful in:

  • time-series analysis
  • event streams
  • order history
  • lifecycle analysis
  • and operational process tracking

This is why they are among the highest-value window functions to learn.

Example: percentage of total

A very useful window function pattern is calculating each row’s contribution to a larger total.

Example:

SELECT
    sale_id,
    sales_rep,
    amount,
    amount * 1.0 / SUM(amount) OVER () AS share_of_total
FROM sales;

This shows how much each row contributes to the full dataset total.

You can also do this within groups:

SELECT
    sale_id,
    sales_rep,
    region,
    amount,
    amount * 1.0 / SUM(amount) OVER (PARTITION BY region) AS share_of_region
FROM sales;

This is very useful for:

  • market share
  • revenue share
  • category contribution
  • and weighted reporting

Example: first value and last value

Window functions also include functions like:

  • FIRST_VALUE()
  • LAST_VALUE()

Conceptually, these can return:

  • the first value in the ordered window
  • the last value in the ordered window

Example idea:

SELECT
    sales_rep,
    sale_date,
    amount,
    FIRST_VALUE(amount) OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS first_sale_amount
FROM sales;

This is useful when you want to compare every row to:

  • the first event
  • the baseline value
  • or the starting point of a sequence

These are slightly more advanced, but very valuable in analytical work.

Window frames matter too

This is where window functions become more advanced.

By default, some window functions use a default frame based on the partition and order. But you can also define a custom window frame to control exactly which rows are included.

Example idea:

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

This is commonly used for a classic running total.

A window frame lets you say:

  • include all previous rows
  • include only the last 3 rows
  • include current row plus following rows
  • and so on

This is especially useful for:

  • moving averages
  • rolling windows
  • bounded comparisons
  • and finer analytical control

Example: moving average

A moving average is a classic window function use case.

Example concept:

SELECT
    sale_date,
    amount,
    AVG(amount) OVER (
        ORDER BY sale_date, sale_id
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS three_row_moving_avg
FROM sales;

This means:

  • for each row
  • average the current row and the two previous rows

This is useful for:

  • smoothing trends
  • reducing noise
  • monitoring performance
  • and time-series analysis

Moving averages are one of the most important advanced analytical patterns in SQL.

Window functions are not only for analysts

A lot of people think window functions are mainly for BI or analytics. That is not true.

Backend developers use them for:

  • latest-row-per-entity logic
  • deduplication
  • first and last events
  • sequencing workflows
  • top-N items per user or group
  • and event stream logic

Data engineers use them for:

  • transformations
  • ranking
  • quality checks
  • and staged modeling

So window functions are practical across many technical roles.

Common window functions you should know

The most useful ones to learn first are:

Ranking

  • ROW_NUMBER()
  • RANK()
  • DENSE_RANK()
  • LAG()
  • LEAD()

Aggregates used as window functions

  • SUM() OVER (...)
  • AVG() OVER (...)
  • COUNT() OVER (...)
  • MIN() OVER (...)
  • MAX() OVER (...)

Value functions

  • FIRST_VALUE()
  • LAST_VALUE()

If you understand these well, you already cover a large portion of practical window-function use.

Common mistakes with window functions

There are a few recurring mistakes that cause a lot of confusion.

1. Confusing window functions with GROUP BY

This is the biggest one.

Window functions keep the original rows. GROUP BY collapses them.

If you forget that distinction, the whole query logic becomes harder to reason about.

2. Forgetting PARTITION BY when the calculation should restart per group

If the ranking or running total should restart by:

  • customer
  • region
  • department
  • category

then forgetting PARTITION BY will create one global calculation instead.

That is a very common business-logic mistake.

3. Forgetting ORDER BY when sequence matters

Functions like:

  • ROW_NUMBER
  • LAG
  • LEAD
  • running totals

need meaningful ordering.

Without a correct ORDER BY, the logic is incomplete or unstable.

4. Not using tie-breakers

If two rows share the same ordering value, add a tie-breaker when needed.

Example:

ORDER BY sale_date, sale_id

instead of only:

ORDER BY sale_date

This makes the sequence more stable.

5. Misunderstanding window frames

For advanced running and moving calculations, the frame controls exactly which rows are included. If the frame is wrong, the result may not match the intended business rule.

Window functions versus subqueries

A lot of problems solved by window functions can also be solved with subqueries. But window functions are often:

  • clearer
  • more concise
  • and easier to extend

For example:

  • latest row per customer
  • above-group-average comparisons
  • running totals
  • row-to-row differences

These can often be written with subqueries. But window functions usually express the logic more directly.

That is why they are so valuable in intermediate and advanced SQL.

Window functions versus self-joins

Another common comparison is with self-joins.

Before window functions, things like:

  • previous row comparisons
  • next row comparisons
  • running positions
  • and first/last event logic

often required self-joins.

Window functions usually make those patterns:

  • easier to read
  • easier to maintain
  • and less awkward conceptually

That does not mean self-joins are obsolete. It just means window functions are often the better fit for sequence-aware analytics.

Performance thoughts

Window functions are powerful, but they are not free.

They often require:

  • sorting
  • partitioning
  • and processing many rows in sequence

So on very large datasets, they can be expensive if:

  • filters are weak
  • partitions are huge
  • ordering is heavy
  • or the query pulls more columns than needed

That does not make them bad. It just means you should treat them like serious query logic and still think about:

  • indexing
  • filtering early
  • row volume
  • and execution plans

Practical examples

Example 1: department average next to each employee

SELECT
    employee_name,
    department,
    salary,
    AVG(salary) OVER (PARTITION BY department) AS department_avg_salary
FROM employees;

Example 2: running total by customer

SELECT
    customer_id,
    order_date,
    total_amount,
    SUM(total_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date, order_id
    ) AS running_total
FROM orders;

Example 3: latest order per customer

WITH ranked_orders AS (
    SELECT
        o.*,
        ROW_NUMBER() OVER (
            PARTITION BY customer_id
            ORDER BY order_date DESC, order_id DESC
        ) AS rn
    FROM orders o
)
SELECT *
FROM ranked_orders
WHERE rn = 1;

Example 4: previous value with LAG

SELECT
    sales_rep,
    sale_date,
    amount,
    LAG(amount) OVER (
        PARTITION BY sales_rep
        ORDER BY sale_date, sale_id
    ) AS previous_amount
FROM sales;

Example 5: top sales rank per region

SELECT
    sales_rep,
    region,
    amount,
    RANK() OVER (
        PARTITION BY region
        ORDER BY amount DESC
    ) AS region_rank
FROM sales;

These are exactly the kinds of patterns that make window functions so valuable in real work.

FAQ

What is a window function in SQL?

A window function is a SQL function that performs a calculation across a set of related rows while still returning one result row for each original row.

What is the difference between GROUP BY and window functions?

GROUP BY collapses multiple rows into one row per group, while window functions keep the original rows and add calculated values alongside them.

What does OVER mean in SQL window functions?

The OVER clause defines the window of rows the function should look at, including optional PARTITION BY and ORDER BY rules.

When should I use window functions in SQL?

Use window functions when you need running totals, rankings, moving averages, previous or next row comparisons, or per-group calculations without losing row-level detail.

Final thoughts

SQL window functions are one of the most valuable features to learn because they let you do analytical work that is very hard to express cleanly with only basic SELECT, GROUP BY, and JOIN patterns.

The most important things to remember are:

  • window functions keep the original rows
  • the OVER clause defines the calculation context
  • PARTITION BY groups related rows for the window
  • ORDER BY creates sequence inside the window
  • ranking, running totals, moving averages, and row-to-row comparisons are some of the best use cases
  • and many problems that once required awkward joins or subqueries become much clearer with window functions

If you understand those ideas clearly, window functions stop feeling advanced and mysterious and start feeling like what they really are:

one of the most practical tools for serious SQL analysis and reporting.

SQL guides

Explore the connected SQL guides on fundamentals, joins, analytics, performance, interviews, and practical workflows.

Pillar guide

SQL Complete Guide for Beginners and Developers

A complete SQL guide for beginners and developers covering databases, tables, SELECT, WHERE, JOINs, GROUP BY, CASE, subqueries, CTEs, inserts, updates, deletes, indexes, and practical query patterns.

View all SQL guides →

Related posts