Window functions compute a result for each row based on a set of related rows, without collapsing those rows into a single output. Unlike GROUP BY, which returns one row per group, a window function preserves all input rows and attaches the computed value to each one. The number of output rows always equals the number of input rows. Window functions are also known as online analytical processing (OLAP) functions.
How it works
A window function operates over a "window" of rows defined by the OVER clause. For each row in the result, the function looks at the rows in its window, computes a value, and attaches that value to the current row.
This is the key difference from GROUP BY:
-- GROUP BY collapses rows: one output row per country
SELECT country, SUM(profit) AS total
FROM test_window
GROUP BY country;
-- Returns 3 rows (one per country)
-- Window function preserves rows: one output row per input row
SELECT country, SUM(profit) OVER (PARTITION BY country) AS total
FROM test_window;
-- Returns 7 rows (one per input row, with the country total attached)Supported functions
Window functions fall into two categories.
Aggregate functions used as window functions (require the OVER clause):
| Function | Description |
|---|---|
SUM() | Sum of values in the window |
COUNT() | Count of rows in the window |
AVG() | Average of values in the window |
MAX() | Maximum value in the window |
MIN() | Minimum value in the window |
Dedicated window functions (can only be used with OVER):
| Function | Description |
|---|---|
ROW_NUMBER() | Assigns a unique sequential number to each row within the partition; no two rows share the same number |
RANK() | Rank within the partition; tied rows get the same rank, with gaps after ties |
DENSE_RANK() | Rank within the partition; tied rows get the same rank, with no gaps |
PERCENT_RANK() | Relative rank as a percentage: (rank - 1) / (total rows - 1) |
CUME_DIST() | Cumulative distribution: fraction of partition rows with values less than or equal to the current row's value |
FIRST_VALUE() | Value of the first row in the current window frame |
LAST_VALUE() | Value of the last row in the current window frame |
LAG() | Value from the row N positions before the current row |
LEAD() | Value from the row N positions after the current row |
NTH_VALUE() | Value from the Nth row of the current window frame |
For full parameter signatures of each dedicated function, see Window function descriptions.
Syntax
FUNCTION OVER ([[PARTITION BY column1] [ORDER BY column2] [RANGE|ROWS BETWEEN start AND end]])| Parameter | Description |
|---|---|
PARTITION BY column1 | Divides the result set into partitions. The window function is applied independently within each partition, similar to how GROUP BY groups rows. Complex expressions are not supported — reference a column directly (for example, country), not country + 1. |
ORDER BY column2 | Defines the order in which rows within each partition are processed. Complex expressions are not supported — reference a column directly. Required when using RANK() or DENSE_RANK(). |
RANGE|ROWS BETWEEN start AND end | Defines the window frame: the subset of rows within the partition that the function considers for each row. See Window frames. |
Window frames
The BETWEEN start AND end clause defines the boundaries of the window frame. Use ROWS to define the frame by physical row positions, or RANGE to define it by value range relative to the current row's sort value.
Valid values for `start`:
| Value | Description |
|---|---|
CURRENT ROW | The frame starts at the current row |
N PRECEDING | The frame starts N rows before the current row |
UNBOUNDED PRECEDING | The frame starts at the first row of the partition |
Valid values for `end`:
| Value | Description |
|---|---|
CURRENT ROW | The frame ends at the current row |
N FOLLOWING | The frame ends N rows after the current row |
UNBOUNDED FOLLOWING | The frame ends at the last row of the partition |
Limitations
Window functions can only appear in the
SELECTlist.An aggregate function and a window function cannot coexist in the same
SELECTlist unless the aggregate function itself uses anOVERclause. To combine them, wrap the window function in a subquery:-- Invalid: SUM(NAME) has no OVER clause SELECT SUM(NAME), COUNT() OVER(...) FROM <table_name> -- Valid: move the window function into a subquery SELECT SUM(NAME), WIN1 FROM (SELECT NAME, COUNT() OVER(...) AS WIN1 FROM <table_name>) aliasThe
PARTITION BYandORDER BYclauses inside anOVERclause do not support complex expressions. Reference columns directly.
Examples
The following examples use this table:
CREATE TABLE test_window (
year INT NOT NULL,
country VARCHAR(50) NOT NULL,
product VARCHAR(100) NOT NULL,
profit INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO test_window (year, country, product, profit) VALUES
(2001, 'Finland', 'Phone', 10),
(2000, 'Finland', 'Computer', 1500),
(2001, 'USA', 'Calculator', 50),
(2001, 'USA', 'Computer', 1500),
(2000, 'Singapore', 'Calculator', 75),
(2000, 'Singapore', 'Calculator', 75),
(2001, 'Singapore', 'Calculator', 79);Calculate total profit per country
Use SUM() with PARTITION BY to attach the country-level total to every row:
SELECT
country,
SUM(profit) OVER (PARTITION BY country) AS sum_profit
FROM test_window;Result:
+-----------+------------+
| country | sum_profit |
+-----------+------------+
| Finland | 1510 |
| Finland | 1510 |
| USA | 1550 |
| USA | 1550 |
| Singapore | 229 |
| Singapore | 229 |
| Singapore | 229 |
+-----------+------------+All 7 input rows are returned. Each row has the total profit for its country attached.
Rank products by profit within each country
Use RANK() with PARTITION BY and ORDER BY to assign a rank to each product within its country:
SELECT
year,
country,
product,
profit,
RANK() OVER (PARTITION BY country ORDER BY profit) AS rank
FROM test_window;Result:
+------+-----------+------------+--------+------+
| year | country | product | profit | rank |
+------+-----------+------------+--------+------+
| 2001 | Finland | Phone | 10 | 1 |
| 2000 | Finland | Computer | 1500 | 2 |
| 2000 | Singapore | Calculator | 75 | 1 |
| 2000 | Singapore | Calculator | 75 | 1 |
| 2001 | Singapore | Calculator | 79 | 3 |
| 2001 | USA | Calculator | 50 | 1 |
| 2001 | USA | Computer | 1500 | 2 |
+------+-----------+------------+--------+------+The two Singapore rows with profit 75 both get rank 1. The next rank is 3 (not 2) because RANK() skips rank values after ties.
Calculate a running total within each country
Use SUM() with a ROWS frame to compute a cumulative sum up to the current row:
SELECT
year,
country,
profit,
SUM(profit) OVER (
PARTITION BY country
ORDER BY 'year'
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS sum_win
FROM test_window;Result:
+------+-----------+--------+---------+
| year | country | profit | sum_win |
+------+-----------+--------+---------+
| 2001 | Finland | 10 | 10 |
| 2000 | Finland | 1500 | 1510 |
| 2000 | Singapore | 75 | 75 |
| 2000 | Singapore | 75 | 150 |
| 2001 | Singapore | 79 | 229 |
| 2001 | USA | 50 | 50 |
| 2001 | USA | 1500 | 1550 |
+------+-----------+--------+---------+ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW includes all rows from the start of the partition through the current row, producing a running total.