All Products
Search
Document Center

AnalyticDB:Window functions

Last Updated:Mar 28, 2026

Window functions compute values across a set of rows related to the current row without collapsing those rows into a single output row. Use them for group rankings, moving averages, cumulative sums, and row-to-row comparisons. Window functions run after the HAVING clause and before the ORDER BY clause. A window function is triggered by using an OVER clause to specify a window.

AnalyticDB for MySQL supports three categories of window functions: aggregate functions, sorting functions, and value functions. For aggregate functions used as window functions, see Aggregate functions.

Supported functions

FunctionCategoryReturn typeDescription
All aggregate functionsAggregateVariesAggregate functions work as window functions when combined with an OVER clause
CUME_DIST()SortingDOUBLECumulative distribution of each value within the partition
RANK()SortingBIGINTRank with gaps for tied values
DENSE_RANK()SortingBIGINTRank without gaps for tied values
NTILE(n)SortingBIGINTDistributes rows into n buckets
ROW_NUMBER()SortingBIGINTSequential row number within the partition, starting from 1
PERCENT_RANK()SortingDOUBLERank as a percentage: (r - 1) / (n - 1)
FIRST_VALUE(x)ValueSame as inputValue of the first row in the window partition
LAST_VALUE(x)ValueSame as inputValue of the last row in the window frame
LAG(x[, offset[, default]])ValueSame as inputValue from offset rows before the current row
LEAD(x[, offset[, default]])ValueSame as inputValue from offset rows after the current row
NTH_VALUE(x, offset)ValueSame as inputValue of the row at position offset within the window frame

Syntax

function_name OVER ([PARTITION BY expr] ORDER BY expr [RANGE|ROWS BETWEEN start AND end])

A window function call has three parts:

  • Partition rule (optional): Divides input rows into independent partitions, similar to GROUP BY. Each partition is processed separately.

  • Sorting rule: Determines the order in which rows are processed within each partition.

  • Window frame: Defines the subset of rows within the partition on which the function operates. The frame is anchored to the current row.

Window frame modes

The window frame can be defined in two modes:

ModeDefinesExample use
ROWSA fixed number of rows relative to the current rowRunning totals, moving averages
RANGEA range of values relative to the current row's valueValue-based sliding windows

Use BETWEEN start AND end to set the frame boundaries:

BoundaryMeaning
CURRENT ROWThe current row
N PRECEDINGN rows before the current row
UNBOUNDED PRECEDINGFrom the first row of the partition to the current row
N FOLLOWINGN rows after the current row
UNBOUNDED FOLLOWINGFrom the current row to the last row of the partition

The following diagram shows how boundaries relate to the current row within a partition:

      PARTITION
+─────────────────+  <- UNBOUNDED PRECEDING (start of partition)
|                 |
|=================|  <- N PRECEDING  -+
|   rows before   |                   |
|   current row   |                   |  FRAME
|~~~~~~~~~~~~~~~~~|  <- CURRENT ROW   |
|   rows after    |                   |
|   current row   |                   |
|=================|  <- N FOLLOWING  -+
|                 |
+─────────────────+  <- UNBOUNDED FOLLOWING (end of partition)

Default window frame behavior

  • LAST_VALUE: The default frame ends at CURRENT ROW, so by default LAST_VALUE returns the current row's value — not the last row in the partition. To get the actual last value, explicitly specify ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.

Always specify the window frame explicitly to avoid unexpected results.

Set up the example table

The examples in this topic use a testwindow table. Run the following statements to create and populate it:

CREATE TABLE testwindow (
  year    INT,
  country VARCHAR(20),
  product VARCHAR(20),
  profit  INT
) DISTRIBUTED BY HASH(year);

INSERT INTO testwindow VALUES (2000, 'Finland', 'Computer',   1500);
INSERT INTO testwindow VALUES (2001, 'Finland', 'Phone',        10);
INSERT INTO testwindow VALUES (2000, 'Germany', 'Calculator',   75);
INSERT INTO testwindow VALUES (2000, 'Germany', 'Calculator',   75);
INSERT INTO testwindow VALUES (2001, 'Germany', 'Calculator',   79);
INSERT INTO testwindow VALUES (2001, 'USA',     'Calculator',   50);
INSERT INTO testwindow VALUES (2001, 'USA',     'Computer',   1500);

Verify the data:

SELECT * FROM testwindow;
+------+---------+------------+--------+
| year | country | product    | profit |
+------+---------+------------+--------+
| 2000 | Finland | Computer   |   1500 |
| 2001 | Finland | Phone      |     10 |
| 2000 | Germany | Calculator |     75 |
| 2000 | Germany | Calculator |     75 |
| 2001 | Germany | Calculator |     79 |
| 2001 | USA     | Calculator |     50 |
| 2001 | USA     | Computer   |   1500 |
+------+---------+------------+--------+

Usage notes

General frame constraints

The following BETWEEN start AND end combinations are invalid:

Invalid combinationError message
start = UNBOUNDED FOLLOWINGWindow frame start cannot be UNBOUNDED FOLLOWING
end = UNBOUNDED PRECEDINGWindow frame end cannot be UNBOUNDED PRECEDING
start = CURRENT ROW, end = N PRECEDINGWindow frame starting from CURRENT ROW cannot end with PRECEDING
start = N FOLLOWING, end = N PRECEDINGWindow frame starting from FOLLOWING cannot end with PRECEDING
start = N FOLLOWING, end = CURRENT ROWWindow frame starting from FOLLOWING cannot end with CURRENT ROW

RANGE mode constraints

In RANGE mode, only UNBOUNDED boundaries are supported:

Invalid combinationError message
start or end = N PRECEDINGWindow frame RANGE PRECEDING is only supported with UNBOUNDED
start or end = N FOLLOWINGWindow frame RANGE FOLLOWING is only supported with UNBOUNDED

Aggregate functions

All aggregate functions become window functions when you add an OVER clause. The function computes its result over the rows in the current sliding window rather than collapsing all rows into one.

The following example computes a rolling sum of order prices by date for each clerk:

SELECT
  clerk,
  orderdate,
  orderkey,
  totalprice,
  SUM(totalprice) OVER (PARTITION BY clerk ORDER BY orderdate) AS rolling_sum
FROM orders
ORDER BY clerk, orderdate, orderkey;

The following example computes a running cumulative sum of profit within each country:

SELECT
  year,
  country,
  profit,
  SUM(profit) OVER (
    PARTITION BY country
    ORDER BY year
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM testwindow;
+------+---------+--------+---------------+
| year | country | profit | running_total |
+------+---------+--------+---------------+
| 2001 | USA     |     50 |            50 |
| 2001 | USA     |   1500 |          1550 |
| 2000 | Germany |     75 |            75 |
| 2000 | Germany |     75 |           150 |
| 2001 | Germany |     79 |           229 |
| 2000 | Finland |   1500 |          1500 |
| 2001 | Finland |     10 |          1510 |
+------+---------+--------+---------------+

Without ORDER BY and a frame clause, the aggregate covers the entire partition:

SELECT country, SUM(profit) OVER (PARTITION BY country) AS total_profit
FROM testwindow;
+---------+--------------+
| country | total_profit |
+---------+--------------+
| Germany |          229 |
| Germany |          229 |
| Germany |          229 |
| USA     |         1550 |
| USA     |         1550 |
| Finland |         1510 |
| Finland |         1510 |
+---------+--------------+

CUME_DIST

CUME_DIST()

Returns the cumulative distribution of each value within a partition: the fraction of rows with values less than or equal to the current row's value.

  • Return type: DOUBLE

  • Tied values receive the same distribution value.

Example:

SELECT
  year,
  country,
  product,
  profit,
  CUME_DIST() OVER (PARTITION BY country ORDER BY profit) AS cume_dist
FROM testwindow;
+------+---------+------------+--------+--------------------+
| year | country | product    | profit | cume_dist          |
+------+---------+------------+--------+--------------------+
| 2001 | USA     | Calculator |     50 |                0.5 |
| 2001 | USA     | Computer   |   1500 |                1.0 |
| 2001 | Finland | Phone      |     10 |                0.5 |
| 2000 | Finland | Computer   |   1500 |                1.0 |
| 2000 | Germany | Calculator |     75 | 0.6666666666666666 |
| 2000 | Germany | Calculator |     75 | 0.6666666666666666 |
| 2001 | Germany | Calculator |     79 |                1.0 |
+------+---------+------------+--------+--------------------+

RANK

RANK()

Returns the rank of each row within its partition, ordered by the ORDER BY expression. The rank equals the number of rows preceding the current row plus one. Tied values receive the same rank, and the next rank skips accordingly — producing gaps in the sequence.

  • Return type: BIGINT

Example:

SELECT
  year,
  country,
  product,
  profit,
  RANK() OVER (PARTITION BY country ORDER BY profit) AS rank
FROM testwindow;
+------+---------+------------+--------+------+
| year | country | product    | profit | rank |
+------+---------+------------+--------+------+
| 2001 | Finland | Phone      |     10 |    1 |
| 2000 | Finland | Computer   |   1500 |    2 |
| 2001 | USA     | Calculator |     50 |    1 |
| 2001 | USA     | Computer   |   1500 |    2 |
| 2000 | Germany | Calculator |     75 |    1 |
| 2000 | Germany | Calculator |     75 |    1 |
| 2001 | Germany | Calculator |     79 |    3 |
+------+---------+------------+--------+------+

In the Germany partition, the two rows with profit = 75 both receive rank 1, and the next row jumps to rank 3.

DENSE_RANK

DENSE_RANK()

Returns the rank of each row within its partition. Unlike RANK(), tied values do not produce gaps — the next rank always follows consecutively.

  • Return type: BIGINT

Example:

SELECT
  year,
  country,
  product,
  profit,
  DENSE_RANK() OVER (PARTITION BY country ORDER BY profit) AS dense_rank
FROM testwindow;
+------+---------+------------+--------+------------+
| year | country | product    | profit | dense_rank |
+------+---------+------------+--------+------------+
| 2001 | Finland | Phone      |     10 |          1 |
| 2000 | Finland | Computer   |   1500 |          2 |
| 2001 | USA     | Calculator |     50 |          1 |
| 2001 | USA     | Computer   |   1500 |          2 |
| 2000 | Germany | Calculator |     75 |          1 |
| 2000 | Germany | Calculator |     75 |          1 |
| 2001 | Germany | Calculator |     79 |          2 |
+------+---------+------------+--------+------------+

In the Germany partition, the two rows with profit = 75 both receive rank 1, and the next row receives rank 2 (no gap).

NTILE

NTILE(n)

Divides rows within each partition into n buckets, numbered 1 through n. If the rows do not divide evenly, the extra rows are distributed one per bucket starting from bucket 1.

For example, 6 rows into 4 buckets: 1, 1, 2, 2, 3, 4.

  • Return type: BIGINT

Example (2 buckets per country):

SELECT
  year,
  country,
  product,
  profit,
  NTILE(2) OVER (PARTITION BY country ORDER BY profit) AS bucket
FROM testwindow;
+------+---------+------------+--------+--------+
| year | country | product    | profit | bucket |
+------+---------+------------+--------+--------+
| 2001 | USA     | Calculator |     50 |      1 |
| 2001 | USA     | Computer   |   1500 |      2 |
| 2001 | Finland | Phone      |     10 |      1 |
| 2000 | Finland | Computer   |   1500 |      2 |
| 2000 | Germany | Calculator |     75 |      1 |
| 2000 | Germany | Calculator |     75 |      1 |
| 2001 | Germany | Calculator |     79 |      2 |
+------+---------+------------+--------+--------+

ROW_NUMBER

ROW_NUMBER()

Assigns a unique sequential integer to each row within its partition, starting from 1. Unlike RANK(), no two rows share the same number.

  • Return type: BIGINT

Example:

SELECT
  year,
  country,
  product,
  profit,
  ROW_NUMBER() OVER (PARTITION BY country) AS row_num
FROM testwindow;
+------+---------+------------+--------+---------+
| year | country | product    | profit | row_num |
+------+---------+------------+--------+---------+
| 2001 | USA     | Calculator |     50 |       1 |
| 2001 | USA     | Computer   |   1500 |       2 |
| 2000 | Germany | Calculator |     75 |       1 |
| 2000 | Germany | Calculator |     75 |       2 |
| 2001 | Germany | Calculator |     79 |       3 |
| 2000 | Finland | Computer   |   1500 |       1 |
| 2001 | Finland | Phone      |     10 |       2 |
+------+---------+------------+--------+---------+

PERCENT_RANK

PERCENT_RANK()

Returns the relative rank of each row as a value between 0 and 1, using the formula (r - 1) / (n - 1), where r is the RANK() of the current row and n is the total number of rows in the partition.

  • Return type: DOUBLE

Example:

SELECT
  year,
  country,
  product,
  profit,
  PERCENT_RANK() OVER (PARTITION BY country ORDER BY profit) AS pct_rank
FROM testwindow;
+------+---------+------------+--------+----------+
| year | country | product    | profit | pct_rank |
+------+---------+------------+--------+----------+
| 2001 | Finland | Phone      |     10 |      0.0 |
| 2000 | Finland | Computer   |   1500 |      1.0 |
| 2001 | USA     | Calculator |     50 |      0.0 |
| 2001 | USA     | Computer   |   1500 |      1.0 |
| 2000 | Germany | Calculator |     75 |      0.0 |
| 2000 | Germany | Calculator |     75 |      0.0 |
| 2001 | Germany | Calculator |     79 |      1.0 |
+------+---------+------------+--------+----------+

FIRST_VALUE

FIRST_VALUE(x)

Returns the value of the first row within the window partition.

  • Return type: Same as the input argument type

Example:

SELECT
  year,
  country,
  product,
  profit,
  FIRST_VALUE(profit) OVER (PARTITION BY country ORDER BY profit) AS first_profit
FROM testwindow;
+------+---------+------------+--------+--------------+
| year | country | product    | profit | first_profit |
+------+---------+------------+--------+--------------+
| 2000 | Germany | Calculator |     75 |           75 |
| 2000 | Germany | Calculator |     75 |           75 |
| 2001 | Germany | Calculator |     79 |           75 |
| 2001 | USA     | Calculator |     50 |           50 |
| 2001 | USA     | Computer   |   1500 |           50 |
| 2001 | Finland | Phone      |     10 |           10 |
| 2000 | Finland | Computer   |   1500 |           10 |
+------+---------+------------+--------+--------------+

LAST_VALUE

LAST_VALUE(x)

Returns the value of the last row within the window frame. The default window frame is ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, so by default LAST_VALUE returns the current row's value, not the last row in the partition.

To return the actual last value in the partition, add an explicit frame clause:

LAST_VALUE(x) OVER (
  PARTITION BY ...
  ORDER BY ...
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
  • Return type: Same as the input argument type

Example 1 — default frame (returns current row's value):

SELECT
  year,
  country,
  product,
  profit,
  LAST_VALUE(profit) OVER (PARTITION BY country ORDER BY profit) AS last_val
FROM testwindow;
+------+---------+------------+--------+----------+
| year | country | product    | profit | last_val |
+------+---------+------------+--------+----------+
| 2001 | USA     | Calculator |     50 |       50 |
| 2001 | USA     | Computer   |   1500 |     1500 |
| 2001 | Finland | Phone      |     10 |       10 |
| 2000 | Finland | Computer   |   1500 |     1500 |
| 2000 | Germany | Calculator |     75 |       75 |
| 2000 | Germany | Calculator |     75 |       75 |
| 2001 | Germany | Calculator |     79 |       79 |
+------+---------+------------+--------+----------+

Example 2 — explicit full-partition frame (returns last row's value):

SELECT
  year,
  country,
  product,
  profit,
  LAST_VALUE(profit) OVER (
    PARTITION BY country
    ORDER BY profit
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS last_val
FROM testwindow;
+------+---------+------------+--------+----------+
| year | country | product    | profit | last_val |
+------+---------+------------+--------+----------+
| 2001 | Finland | Phone      |     10 |     1500 |
| 2000 | Finland | Computer   |   1500 |     1500 |
| 2000 | Germany | Calculator |     75 |       79 |
| 2000 | Germany | Calculator |     75 |       79 |
| 2001 | Germany | Calculator |     79 |       79 |
| 2001 | USA     | Calculator |     50 |     1500 |
| 2001 | USA     | Computer   |   1500 |     1500 |
+------+---------+------------+--------+----------+

LAG

LAG(x[, offset[, default_value]])

Returns the value from the row that is offset rows before the current row within the partition.

ParameterDescriptionDefault
xThe column or expression to evaluate
offsetNumber of rows to look back; 0 refers to the current row1
default_valueValue returned when offset is NULL or exceeds the partition sizeNULL
  • Return type: Same as the input argument type

Example (look back 1 row):

SELECT
  year,
  country,
  product,
  profit,
  LAG(profit) OVER (PARTITION BY country ORDER BY profit) AS prev_profit
FROM testwindow;
+------+---------+------------+--------+-------------+
| year | country | product    | profit | prev_profit |
+------+---------+------------+--------+-------------+
| 2001 | USA     | Calculator |     50 |        NULL |
| 2001 | USA     | Computer   |   1500 |          50 |
| 2000 | Germany | Calculator |     75 |        NULL |
| 2000 | Germany | Calculator |     75 |          75 |
| 2001 | Germany | Calculator |     79 |          75 |
| 2001 | Finland | Phone      |     10 |        NULL |
| 2000 | Finland | Computer   |   1500 |          10 |
+------+---------+------------+--------+-------------+

LEAD

LEAD(x[, offset[, default_value]])

Returns the value from the row that is offset rows after the current row within the partition.

ParameterDescriptionDefault
xThe column or expression to evaluate
offsetNumber of rows to look ahead; 0 refers to the current row1
default_valueValue returned when offset is NULL or exceeds the partition sizeNULL
  • Return type: Same as the input argument type

Example (look ahead 1 row):

SELECT
  year,
  country,
  product,
  profit,
  LEAD(profit) OVER (PARTITION BY country ORDER BY profit) AS next_profit
FROM testwindow;
+------+---------+------------+--------+-------------+
| year | country | product    | profit | next_profit |
+------+---------+------------+--------+-------------+
| 2000 | Germany | Calculator |     75 |          75 |
| 2000 | Germany | Calculator |     75 |          79 |
| 2001 | Germany | Calculator |     79 |        NULL |
| 2001 | Finland | Phone      |     10 |        1500 |
| 2000 | Finland | Computer   |   1500 |        NULL |
| 2001 | USA     | Calculator |     50 |        1500 |
| 2001 | USA     | Computer   |   1500 |        NULL |
+------+---------+------------+--------+-------------+

NTH_VALUE

NTH_VALUE(x, offset)

Returns the value of the row at position offset within the window frame. The offset starts from 1.

  • If offset is NULL or exceeds the number of rows in the frame, NULL is returned.

  • If offset is 0 or negative, an error is returned.

  • Return type: Same as the input argument type

Example (first row in each partition, equivalent to FIRST_VALUE):

SELECT
  year,
  country,
  product,
  profit,
  NTH_VALUE(profit, 1) OVER (PARTITION BY country ORDER BY profit) AS nth_val
FROM testwindow;
+------+---------+------------+--------+---------+
| year | country | product    | profit | nth_val |
+------+---------+------------+--------+---------+
| 2001 | Finland | Phone      |     10 |      10 |
| 2000 | Finland | Computer   |   1500 |      10 |
| 2001 | USA     | Calculator |     50 |      50 |
| 2001 | USA     | Computer   |   1500 |      50 |
| 2000 | Germany | Calculator |     75 |      75 |
| 2000 | Germany | Calculator |     75 |      75 |
| 2001 | Germany | Calculator |     79 |      75 |
+------+---------+------------+--------+---------+

What's next