Window functions

更新时间:
复制 MD 格式

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):

FunctionDescription
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):

FunctionDescription
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]])
ParameterDescription
PARTITION BY column1Divides 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 column2Defines 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 endDefines 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`:

ValueDescription
CURRENT ROWThe frame starts at the current row
N PRECEDINGThe frame starts N rows before the current row
UNBOUNDED PRECEDINGThe frame starts at the first row of the partition

Valid values for `end`:

ValueDescription
CURRENT ROWThe frame ends at the current row
N FOLLOWINGThe frame ends N rows after the current row
UNBOUNDED FOLLOWINGThe frame ends at the last row of the partition

Limitations

  • Window functions can only appear in the SELECT list.

  • An aggregate function and a window function cannot coexist in the same SELECT list unless the aggregate function itself uses an OVER clause. 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>) alias
  • The PARTITION BY and ORDER BY clauses inside an OVER clause 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.