SQL Window Functions perform calculations across a set of rows related to the current row within a query result. Unlike aggregate functions, window functions do not collapse rows; instead, they retain individual rows while calculating values over a specific window or partition.
<window_function>(expression)
OVER (
PARTITION BY column_name
ORDER BY column_name
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
- PARTITION BY: Divides rows into groups (optional).
- ORDER BY: Defines the order of rows within each partition.
- ROWS BETWEEN: Specifies the window frame relative to the current row.
- ROW_NUMBER() - Assigns a unique rank to each row.
SELECT name, department, salary,
ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary DESC) AS row_num
FROM employees;
- RANK() - Assigns ranks; ties get the same rank but skip numbers.
SELECT name, salary,
RANK() OVER(ORDER BY salary DESC) AS rank
FROM employees;
- DENSE_RANK() - Assigns ranks; ties get the same rank but do not skip numbers.
SELECT name, salary,
DENSE_RANK() OVER(ORDER BY salary DESC) AS dense_rank
FROM employees;
- NTILE(n) - Divides rows into n equal buckets.
SELECT name, salary,
NTILE(4) OVER(ORDER BY salary DESC) AS quartile
FROM employees;
Perform calculations within a window frame without collapsing rows.
- SUM() - Cumulative sum.
SELECT name, salary,
SUM(salary) OVER(PARTITION BY department ORDER BY salary) AS cum_sum
FROM employees;
- AVG() - Moving average.
SELECT name, salary,
AVG(salary) OVER(PARTITION BY department ORDER BY salary ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM employees;
- MIN() / MAX() - Running minimum/maximum.
SELECT name, salary,
MIN(salary) OVER(PARTITION BY department ORDER BY salary) AS min_salary,
MAX(salary) OVER(PARTITION BY department ORDER BY salary) AS max_salary
FROM employees;
- COUNT() - Cumulative count.
SELECT name, salary,
COUNT(*) OVER(PARTITION BY department ORDER BY salary) AS running_count
FROM employees;
- LAG() - Accesses previous row's value.
SELECT name, salary,
LAG(salary, 1, 0) OVER(ORDER BY salary) AS prev_salary
FROM employees;
- LEAD() - Accesses next row's value.
SELECT name, salary,
LEAD(salary, 1, 0) OVER(ORDER BY salary) AS next_salary
FROM employees;
- FIRST_VALUE() - Returns the first value in the window.
SELECT name, salary,
FIRST_VALUE(salary) OVER(ORDER BY salary) AS lowest_salary
FROM employees;
- LAST_VALUE() - Returns the last value in the window.
SELECT name, salary,
LAST_VALUE(salary) OVER(ORDER BY salary ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS highest_salary
FROM employees;
- ROWS: Counts rows physically (fixed window size).
- RANGE: Considers rows with same values in ordering (dynamic size).
Examples:
- Cumulative Sum (ROWS):
SELECT name, salary,
SUM(salary) OVER(ORDER BY salary ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_sum
FROM employees;
- Moving Average (RANGE):
SELECT name, salary,
AVG(salary) OVER(ORDER BY salary RANGE BETWEEN INTERVAL 1 DAY PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales;
- Find Top-N Rows per Group:
SELECT name, department, salary
FROM (
SELECT name, department, salary,
RANK() OVER(PARTITION BY department ORDER BY salary DESC) AS rank
FROM employees
) subquery
WHERE rank <= 3;
- Cumulative Sales Analysis:
SELECT order_date, customer_id, sales_amount,
SUM(sales_amount) OVER(PARTITION BY customer_id ORDER BY order_date) AS cumulative_sales
FROM sales;
- Salary Differences:
SELECT name, salary,
salary - LAG(salary, 1) OVER(ORDER BY salary) AS salary_diff
FROM employees;
- Running Total and Percentages:
SELECT name, salary,
SUM(salary) OVER(ORDER BY salary) AS running_total,
salary * 100.0 / SUM(salary) OVER() AS percent_of_total
FROM employees;
- Preserve Individual Rows: Unlike GROUP BY, it keeps detailed row-level data.
- Flexibility: Supports ranking, cumulative sums, and comparisons without subqueries.
- Performance: Efficient for analytical tasks like time series and reporting dashboards.
CompiledByUdithaWICK