Skip to main content

SQL Window Functions - Complete Guide

What are Window Functions?​

SQL Window Functions perform calculations across a set of rows that are related to the current row, but unlike GROUP BY, they don't collapse rows into a single output. Think of them as "looking through a window" at related rows while keeping all individual rows intact.

note

Key Characteristics of Window Functions:

  • No Row Reduction: Unlike GROUP BY, every row remains in the result set.

  • Contextual Calculations: Perform calculations based on a "window" of related rows.

  • OVER Clause: The signature syntax that defines the window of rows.

  • Powerful Analytics: Perfect for rankings, running totals, comparisons, and trends.

success

Why Window Functions are Game-Changers:

Imagine you have a sales table and want to:

  • Show each sale alongside the total sales for that month
  • Rank salespeople by performance within each region
  • Compare each day's sales to the previous day
  • Calculate a running total of revenue

Without window functions, you'd need complex subqueries or multiple joins. Window functions make these tasks simple and elegant!

Real-World Example: A sales manager wants to see each salesperson's individual sales while also showing their rank within their region and the regional average - all in one query. Window functions make this trivial.

info

Basic Window Function Syntax​

SELECT 
column1,
column2,
WINDOW_FUNCTION() OVER (
[PARTITION BY partition_column]
[ORDER BY sort_column]
[ROWS/RANGE frame_specification]
) AS result_column
FROM table_name;
ComponentPurposeRequired?
WINDOW_FUNCTIONThe calculation to performYes
OVERDefines the windowYes
PARTITION BYGroups rows into partitionsOptional
ORDER BYOrders rows within partitionsOptional*
ROWS/RANGEDefines frame boundariesOptional

*Required for some functions like ROW_NUMBER, RANK, LAG, LEAD

The OVER Clause - Your Window Control Panel​

-- Simple window: entire table
SUM(amount) OVER ()

-- Partitioned window: separate calculations per group
SUM(amount) OVER (PARTITION BY department)

-- Ordered window: enables ranking and sequential functions
ROW_NUMBER() OVER (ORDER BY sales DESC)

-- Complete window: partition + order + frame
SUM(amount) OVER (
PARTITION BY department
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)

Essential Window Functions​

-- Assign unique sequential numbers to rows
SELECT
employee_name,
department,
salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS overall_rank,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;

-- Result: Every row gets a unique number
-- Perfect for: Pagination, removing duplicates, creating unique IDs

-- Practical use: Top 3 earners per department
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
)
SELECT employee_name, department, salary
FROM ranked
WHERE rn <= 3;

Understanding PARTITION BY​

Think of PARTITION BY as creating separate "mini-tables" within your result set. Calculations reset for each partition.

-- Without PARTITION BY: One ranking across entire table
SELECT
employee_name,
department,
salary,
RANK() OVER (ORDER BY salary DESC) AS company_rank
FROM employees;

-- With PARTITION BY: Separate rankings per department
SELECT
employee_name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
tip

PARTITION BY Best Practices:

  • Use when you want separate calculations per group
  • Can partition by multiple columns: PARTITION BY region, department
  • Think of it as "invisible GROUP BY" - groups data without collapsing rows
  • Each partition is processed independently

Frame Specifications - Defining Your Window​

Frame specifications define which rows are included in the calculation.

-- Frame clause syntax
ROWS BETWEEN start_boundary AND end_boundary

-- Common frame specifications:
UNBOUNDED PRECEDING -- From the first row of partition
UNBOUNDED FOLLOWING -- To the last row of partition
CURRENT ROW -- The current row
N PRECEDING -- N rows before current
N FOLLOWING -- N rows after current
-- Running total: Sum from start to current row
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;

-- Shorthand (same result):
SUM(amount) OVER (ORDER BY order_date)

Window Functions Quick Reference​

FunctionPurposeCommon Use Case
ROW_NUMBER()Unique sequential numberPagination, removing duplicates
RANK()Ranking with gapsCompetition standings
DENSE_RANK()Ranking without gapsCategory rankings
NTILE(n)Divide into n groupsCustomer segmentation
LAG()Previous row valuePeriod-over-period comparison
LEAD()Next row valueForecasting, trend analysis
FIRST_VALUE()First value in windowBaseline comparison
LAST_VALUE()Last value in windowFinal value comparison
SUM()Running/windowed totalCumulative sales
AVG()Moving/windowed averageSmoothing trends
COUNT()Windowed countRunning count
MIN()/MAX()Windowed extremesRange analysis

Practice Problems​

Try these on your own to master window functions:

  1. Find the top 3 products by revenue in each category
  2. Calculate each employee's salary as a percentage of their department total
  3. Show month-over-month growth rate for sales
  4. Identify customers whose last 3 purchases were all above $100
  5. Find products that consistently rank in top 10 for 90 consecutive days

Conclusion​

Window functions are incredibly powerful tools that transform how you analyze data in SQL. They allow you to perform complex analytics that would otherwise require multiple queries, subqueries, or even application-level processing.

Start with simple examples like ROW_NUMBER() and SUM() OVER(), then gradually incorporate partitioning, frames, and more advanced functions. With practice, window functions will become your go-to solution for sophisticated data analysis.

Remember:

  • Window functions keep all rows (unlike GROUP BY)
  • The OVER clause defines your window
  • PARTITION BY creates separate calculations per group
  • ORDER BY is crucial for sequential functions
  • Frame specifications control which rows are included

Happy querying! πŸš€