Free — no signup required

Window Functions: The Power User Tool

2 min read

Aggregation Without Collapsing Rows

Standard GROUP BY aggregation is destructive — it collapses many rows into one. If you group orders by region, you lose the individual order details. Sometimes you need both: the aggregate and the individual rows.

Window functions compute an aggregate across a defined set of rows (the "window") while keeping every individual row intact. They use the OVER() clause to define the window.

-- Show each order AND the running total up to that point
SELECT
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;

Result:

order_date  | amount | running_total
------------|--------|---------------
2024-01-01  |    10  |    10
2024-01-02  |    20  |    30
2024-01-03  |     5  |    35

Every row is preserved. The running total accumulates. This is impossible with GROUP BY.

Defining the Window: PARTITION BY and ORDER BY

The OVER() clause accepts two optional sub-clauses:

  • PARTITION BY — Divides rows into independent groups (like GROUP BY, but without collapsing). The window function resets for each partition.
  • ORDER BY — Defines the order within each partition, which determines how running calculations (like running totals or rankings) accumulate.
-- Running total PER customer (resets for each new customer)
SELECT
    user_id,
    order_date,
    amount,
    SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS customer_running_total
FROM orders;

Ranking Functions

Window functions also include ranking functions — among the most common in analytics and interview questions:

SELECT
    user_id,
    amount,
    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn,
    RANK()       OVER (PARTITION BY user_id ORDER BY amount DESC) AS rnk,
    DENSE_RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS dense_rnk
FROM orders;
Function Behavior on ties
ROW_NUMBER() Always unique — ties get arbitrary sequential numbers
RANK() Ties get the same rank; next rank skips (1, 1, 3)
DENSE_RANK() Ties get the same rank; next rank does not skip (1, 1, 2)

A classic interview problem: "Find the top 3 orders per customer." The solution uses ROW_NUMBER() or RANK() inside a CTE, then filters where rn <= 3.

Interview Tip

"What's the difference between RANK() and DENSE_RANK()?" is a near-universal SQL interview question. The answer: both assign the same rank to tied rows, but RANK() leaves gaps in the sequence (two rows tied at rank 1 means the next rank is 3), while DENSE_RANK() never leaves gaps (the next rank after two tied rows at 1 is 2). Use DENSE_RANK() when you want "top N" results and can't afford to skip ranks due to ties.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free