Window Functions

Window functions compute a value for each row based on a "window" of related rows — without collapsing the result into fewer rows like GROUP BY does. They are the right tool for running totals, rankings, moving averages, and any calculation that needs to see neighboring rows.

FeatureQL window functions use the same OVER() clause as SQL. The key difference: in FeatureQL, the window function and its OVER() specification are part of a feature definition, so you can compose them with other features.

Syntax

<feature> := <aggregate>(<expression>) OVER (
    [PARTITION BY <key>, ...]
    [ORDER BY <expression> [ASC|DESC], ...]
)
  • PARTITION BY divides rows into groups (like GROUP BY, but without collapsing). Omit it to treat all rows as one partition.
  • ORDER BY defines the row ordering within each partition. For cumulative functions like SUM(), this turns them into running totals.

SUM() OVER — running totals and partition sums

The following example demonstrates several SUM() OVER(...) variants on the same dataset: a global sum, partition sums, running totals ordered by ID or value, and combinations of partitioning with ordering. Each column shows a different window specification so you can compare them side by side.

FeatureQL
SELECT
    id as C_ID,
    category as C_CATEGORY,
    value as C_VALUE,
    -- Basic SUM variants
    SUM(value) OVER ( )::BIGINT AS sum_all,
    SUM(value) OVER (PARTITION BY category)::BIGINT AS sum_by_category,
    SUM(value) OVER (ORDER BY id)::BIGINT AS sum_running_by_id,
    SUM(value) OVER (ORDER BY value, id)::BIGINT AS sum_running_by_value,
    SUM(value) OVER (PARTITION BY category ORDER BY id)::BIGINT AS sum_running_cat_by_id,
    SUM(value) OVER (PARTITION BY category ORDER BY value, id)::BIGINT AS sum_running_cat_by_value,
    -- SUM with different columns
    SUM(score) OVER (PARTITION BY category) AS sum_score_by_category,
    SUM(id) OVER (ORDER BY value DESC, id)::BIGINT AS sum_id_desc_value,
    -- SUM with descending order
    SUM(value) OVER (ORDER BY value DESC, id)::BIGINT AS sum_running_desc,
    SUM(value) OVER (PARTITION BY category ORDER BY value DESC, id)::BIGINT AS sum_running_cat_desc,
    -- SUM with multiple order by columns
    SUM(value) OVER (ORDER BY category, value, id)::BIGINT AS sum_running_cat_val,
    SUM(value) OVER (PARTITION BY flag ORDER BY value, id)::BIGINT AS sum_by_flag_ordered
FROM FM.TUTORIALS.WF
FOR
    ID := BIND_VALUES(SEQUENCE(1,10))
ORDER BY id;
Result
C_ID BIGINTC_CATEGORY VARCHARC_VALUE BIGINTSUM_ALL BIGINTSUM_BY_CATEGORY BIGINTSUM_RUNNING_BY_ID BIGINTSUM_RUNNING_BY_VALUE BIGINTSUM_RUNNING_CAT_BY_ID BIGINTSUM_RUNNING_CAT_BY_VALUE BIGINTSUM_SCORE_BY_CATEGORY VARCHARSUM_ID_DESC_VALUE BIGINTSUM_RUNNING_DESC BIGINTSUM_RUNNING_CAT_DESC BIGINTSUM_RUNNING_CAT_VAL BIGINTSUM_BY_FLAG_ORDERED BIGINT
1A1020050101510105.548195501010
2A2020050306530305.532135203025
3A2020050508550505.535155405060
4B15200706530151512.039170706525
5B252007090110404012.02190559050
6B3020070120165707012.0166530120115
7C52008012555511.555200801255
8C152008014045202011.5471857514040
9C2520080165135454511.5301156016585
10C3520080200200808011.510353520085

Compare the columns: OVER () gives the same grand total everywhere, PARTITION BY category gives per-category totals, and adding ORDER BY turns the sum into a running total that grows row by row. Combining PARTITION BY with ORDER BY gives a running total that resets at each category boundary.

ARRAY_AGG() OVER — accumulating values

ARRAY_AGG() as a window function collects values into an array as it moves through the window. This is useful for seeing which values have been encountered so far, or for building up context for each row.

FeatureQL
SELECT
    id as C_ID,
    category as C_CATEGORY,
    value as C_VALUE,
    -- Basic ARRAY_AGG variants
    ARRAY_AGG(name) OVER ( ) AS array_all,
    ARRAY_AGG(name) OVER (PARTITION BY category) AS array_by_category,
    ARRAY_AGG(name) OVER (ORDER BY id) AS array_running_by_id,
    ARRAY_AGG(name) OVER (ORDER BY value, id) AS array_running_by_value,
    ARRAY_AGG(name) OVER (PARTITION BY category ORDER BY id) AS array_running_cat_by_id,
    ARRAY_AGG(name) OVER (PARTITION BY category ORDER BY value, id) AS array_running_cat_by_value,
    -- ARRAY_AGG with different columns
    ARRAY_AGG(value) OVER (PARTITION BY category) AS array_values_by_category,
    ARRAY_AGG(id) OVER (ORDER BY value DESC, id) AS array_ids_desc_value,
    -- ARRAY_AGG with descending order
    ARRAY_AGG(name) OVER (ORDER BY value DESC, id) AS array_running_desc,
    ARRAY_AGG(name) OVER (PARTITION BY category ORDER BY value DESC, id) AS array_running_cat_desc,
    -- ARRAY_AGG with multiple order by columns
    ARRAY_AGG(name) OVER (ORDER BY category, value, id) AS array_running_cat_val,
    ARRAY_AGG(name) OVER (PARTITION BY flag ORDER BY value, id) AS array_by_flag_ordered
FROM FM.TUTORIALS.WF
FOR
    ID := BIND_VALUES(SEQUENCE(1,10))
ORDER BY id;
Result
C_ID BIGINTC_CATEGORY VARCHARC_VALUE BIGINTARRAY_ALL ARRAYARRAY_BY_CATEGORY ARRAYARRAY_RUNNING_BY_ID ARRAYARRAY_RUNNING_BY_VALUE ARRAYARRAY_RUNNING_CAT_BY_ID ARRAYARRAY_RUNNING_CAT_BY_VALUE ARRAYARRAY_VALUES_BY_CATEGORY ARRAYARRAY_IDS_DESC_VALUE ARRAYARRAY_RUNNING_DESC ARRAYARRAY_RUNNING_CAT_DESC ARRAYARRAY_RUNNING_CAT_VAL ARRAYARRAY_BY_FLAG_ORDERED ARRAY
1A10[g, c, b, f, y, z, a, e, x, d][x, y, z][x][d, x][x][x][10, 20, 20][10, 6, 5, 9, 2, 3, 4, 8, 1][g, c, b, f, y, z, a, e, x][y, z, x][x][x]
2A20[g, c, b, f, y, z, a, e, x, d][x, y, z][x, y][d, x, a, e, y][x, y][x, y][10, 20, 20][10, 6, 5, 9, 2][g, c, b, f, y][y][x, y][d, y]
3A20[g, c, b, f, y, z, a, e, x, d][x, y, z][x, y, z][d, x, a, e, y, z][x, y, z][x, y, z][10, 20, 20][10, 6, 5, 9, 2, 3][g, c, b, f, y, z][y, z][x, y, z][x, a, e, z]
4B15[g, c, b, f, y, z, a, e, x, d][a, b, c][x, y, z, a][d, x, a][a][a][15, 25, 30][10, 6, 5, 9, 2, 3, 4][g, c, b, f, y, z, a][c, b, a][x, y, z, a][x, a]
5B25[g, c, b, f, y, z, a, e, x, d][a, b, c][x, y, z, a, b][d, x, a, e, y, z, b][a, b][a, b][15, 25, 30][10, 6, 5][g, c, b][c, b][x, y, z, a, b][d, y, b]
6B30[g, c, b, f, y, z, a, e, x, d][a, b, c][x, y, z, a, b, c][d, x, a, e, y, z, b, f, c][a, b, c][a, b, c][15, 25, 30][10, 6][g, c][c][x, y, z, a, b, c][x, a, e, z, f, c]
7C5[g, c, b, f, y, z, a, e, x, d][d, e, f, g][x, y, z, a, b, c, d][d][d][d][5, 15, 25, 35][10, 6, 5, 9, 2, 3, 4, 8, 1, 7][g, c, b, f, y, z, a, e, x, d][g, f, e, d][x, y, z, a, b, c, d][d]
8C15[g, c, b, f, y, z, a, e, x, d][d, e, f, g][x, y, z, a, b, c, d, e][d, x, a, e][d, e][d, e][5, 15, 25, 35][10, 6, 5, 9, 2, 3, 4, 8][g, c, b, f, y, z, a, e][g, f, e][x, y, z, a, b, c, d, e][x, a, e]
9C25[g, c, b, f, y, z, a, e, x, d][d, e, f, g][x, y, z, a, b, c, d, e, f][d, x, a, e, y, z, b, f][d, e, f][d, e, f][5, 15, 25, 35][10, 6, 5, 9][g, c, b, f][g, f][x, y, z, a, b, c, d, e, f][x, a, e, z, f]
10C35[g, c, b, f, y, z, a, e, x, d][d, e, f, g][x, y, z, a, b, c, d, e, f, g][d, x, a, e, y, z, b, f, c, g][d, e, f, g][d, e, f, g][5, 15, 25, 35][10][g][g][x, y, z, a, b, c, d, e, f, g][d, y, b, g]

The same partitioning and ordering patterns apply: ARRAY_AGG(name) OVER (ORDER BY id) builds a growing array as rows are processed, while PARTITION BY category resets the accumulation at each category boundary.

Available window functions

All standard aggregate functions (SUM(), AVG(), MIN(), MAX(), COUNT(), ARRAY_AGG(), etc.) work as window functions when combined with OVER(). FeatureQL also supports dedicated window functions:

FunctionWhat it does
ROW_NUMBER()Sequential number within the partition
RANK()Rank with gaps for ties
DENSE_RANK()Rank without gaps for ties
NTILE(n)Distributes rows into n roughly equal buckets
LEAD(expr, offset)Value from a row offset positions ahead
LAG(expr, offset)Value from a row offset positions behind
FIRST_VALUE(expr)First value in the window frame
LAST_VALUE(expr)Last value in the window frame

For full signatures and parameters, see the Window function reference .

Last update at: 2026/03/03 16:47:38
Last updated: 2026-03-03 16:48:19