🗄️SQL Templates
Window Functions (OVER, PARTITION BY)
ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD with window partitions
Explanation
Perform calculations across row sets without grouping, useful for rankings and running totals.
Examples
Row numbers
Input
ROW_NUMBER() OVER (ORDER BY price)
Output
Sequential numbering
Partition by category
Input
RANK() OVER (PARTITION BY category ORDER BY sales)
Output
Rank within each category
Code Examples
SQL
-- ROW_NUMBER (sequential numbering)
SELECT
name,
price,
ROW_NUMBER() OVER (ORDER BY price DESC) AS row_num
FROM products;
-- RANK (with gaps for ties)
SELECT
name,
price,
RANK() OVER (ORDER BY price DESC) AS rank
FROM products;
-- DENSE_RANK (no gaps for ties)
SELECT
name,
price,
DENSE_RANK() OVER (ORDER BY price DESC) AS dense_rank
FROM products;
-- PARTITION BY (separate ranking per group)
SELECT
category,
name,
price,
RANK() OVER (
PARTITION BY category
ORDER BY price DESC
) AS rank_in_category
FROM products;
-- Top N per group
WITH ranked AS (
SELECT
category,
name,
price,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY price DESC
) AS rn
FROM products
)
SELECT category, name, price
FROM ranked
WHERE rn <= 3;
-- LAG (previous row value)
SELECT
date,
revenue,
LAG(revenue) OVER (ORDER BY date) AS prev_day_revenue,
revenue - LAG(revenue) OVER (ORDER BY date) AS daily_change
FROM daily_sales;
-- LEAD (next row value)
SELECT
date,
revenue,
LEAD(revenue) OVER (ORDER BY date) AS next_day_revenue
FROM daily_sales;
-- Running total (SUM window function)
SELECT
date,
revenue,
SUM(revenue) OVER (
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM daily_sales;
-- Moving average
SELECT
date,
revenue,
AVG(revenue) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7day
FROM daily_sales;
-- FIRST_VALUE and LAST_VALUE
SELECT
date,
revenue,
FIRST_VALUE(revenue) OVER (
ORDER BY date
) AS first_day_revenue,
LAST_VALUE(revenue) OVER (
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_day_revenue
FROM daily_sales;
-- Multiple window functions
SELECT
name,
category,
price,
ROW_NUMBER() OVER (ORDER BY price DESC) AS overall_rank,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY price DESC
) AS category_rank,
price - AVG(price) OVER (
PARTITION BY category
) AS price_diff_from_avg
FROM products;
-- Percentile ranking
SELECT
name,
price,
PERCENT_RANK() OVER (ORDER BY price) AS percentile
FROM products;💡 Tips
- Window functions don't reduce row count (unlike GROUP BY)
- PARTITION BY is optional (entire result set is one partition)
- ORDER BY in OVER is often required
- RANK has gaps after ties, DENSE_RANK doesn't
- LAG/LEAD useful for time series comparisons
- Frame clauses (ROWS/RANGE) control calculation window
- Can combine multiple window functions
⚠️ Common Pitfalls
- Window functions only in SELECT and ORDER BY
- Cannot use in WHERE (use subquery/CTE instead)
- LAST_VALUE needs UNBOUNDED FOLLOWING frame
- Performance can be slow on large datasets
- Frame clause behavior can be unintuitive
- NULL handling in LAG/LEAD needs attention