Muyoy · blog

Mastering SQL Window Functions for Analytics

Mastering SQL Window Functions

In data analytics, summarizing trends and tracking sequential metrics is a daily necessity. Whether calculating rolling averages, identifying consecutive logon streaks, or finding month-over-month growth, standard GROUP BY aggregates are often insufficient.

SQL Window Functions solve these challenges by performing calculations across a set of table rows related to the current row, without collapsing them into a single summary line.

Here is a practical guide to mastering the most common window functions.

1. The Syntax: OVER and PARTITION BY

Every window function relies on the OVER clause, which defines the “window” of data.

SELECT
employee_id,
department,
salary,
AVG(salary) OVER(PARTITION BY department) AS avg_dept_salary
FROM employees;
  • PARTITION BY: Divides the rows into groups (e.g. by department) to calculate the function separately.
  • ORDER BY: Orders the rows within each partition (important for rankings or chronological offsets).

2. Row Rankings: ROW_NUMBER vs. DENSE_RANK

Rank functions allow you to index rows within a group. Knowing the difference between them is crucial to avoiding analytical discrepancies.

FunctionTies HandlingExample Output (for salaries 100, 100, 90)
ROW_NUMBER()Arbitrary indices, no duplicate ranks1, 2, 3
RANK()Same rank for ties, skips subsequent rank numbers1, 1, 3
DENSE_RANK()Same rank for ties, does NOT skip rank numbers1, 1, 2
SELECT
product_id,
sales_amount,
DENSE_RANK() OVER(ORDER BY sales_amount DESC) AS sales_rank
FROM product_sales;

3. Offsets: LAG and LEAD

LAG fetches data from a previous row in the partition, while LEAD fetches data from a subsequent row. These are essential for tracking chronological changes.

-- Calculating Month-over-Month Sales Growth
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', order_date) AS order_month,
SUM(total_amount) AS revenue
FROM orders
GROUP BY 1
)
SELECT
order_month,
revenue,
LAG(revenue, 1) OVER (ORDER BY order_month) AS prev_month_revenue,
revenue - LAG(revenue, 1) OVER (ORDER BY order_month) AS mom_growth
FROM monthly_revenue;

4. Rolling Sums and Cumulative Moving Averages

By extending the ORDER BY clause within OVER, you instruct SQL to evaluate values cumulatively from the start of the partition up to the current row:

SELECT
transaction_date,
amount,
SUM(amount) OVER (ORDER BY transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_balance
FROM bank_transactions;

Window functions allow you to compute complex analytics directly within the database engine, minimizing data transfer overhead and speeding up report generation.