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_salaryFROM 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.
| Function | Ties Handling | Example Output (for salaries 100, 100, 90) |
|---|---|---|
ROW_NUMBER() | Arbitrary indices, no duplicate ranks | 1, 2, 3 |
RANK() | Same rank for ties, skips subsequent rank numbers | 1, 1, 3 |
DENSE_RANK() | Same rank for ties, does NOT skip rank numbers | 1, 1, 2 |
SELECT product_id, sales_amount, DENSE_RANK() OVER(ORDER BY sales_amount DESC) AS sales_rankFROM 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 GrowthWITH 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_growthFROM 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_balanceFROM bank_transactions;Window functions allow you to compute complex analytics directly within the database engine, minimizing data transfer overhead and speeding up report generation.