HomeIntegrationsPricingLearn
Sign inGet started

Data to decisions, faster.

Book a Demo

Learn

  • Blog
  • Webinars
  • SQL tutorials

Legal & security

  • Privacy Policy
  • Terms of Use
  • Cookies Policy
  • Trust Center
  • Security

© 2026 Count Technologies Ltd. All rights reserved.

SQL Resources

Count is the best SQL IDE, wrapped up in the best data analysis tool, all inside the best BI platform.

ARRAY_AGG
Arrays Explained
Arrays Explained
CASE
CAST
COALESCE
CONCAT
COUNT [DISTINCT]
CURRENT_DATE
CURRENT_DATETIME
CURRENT_TIMESTAMP
DATETIME_DIFF
DATETIME_TRUNC
DATE_DIFF
DATE_TRUNC
Data Types
Dates and Times
EXTRACT
IF
MEDIAN
SUBSTR
String Functions Explained
TIMESTAMP_DIFF
TIMESTAMP_TRUNC
TIME_DIFF
TIME_TRUNC
UNNEST
Window Functions Explained
IntroductionWindow Functions
SQL Resources/BigQuery Standard SQL/Window Functions Explained

Window Functions Explained

Everything you need to know about SQL Array functions. Arrays are ordered lists in BigQuery. They are very powerful and flexible once you know how to use them.

Introduction

Window functions, or Analytic functions as they're called in BigQuery, are a way to compute values over a group of rows. They return a single value for each row, in contrast to aggregate functions which returns a single value for a group of rows.

Aggregate vs window/analytic functions:

letteraggregate
A5
C6
letterwindow
A5
A5
C6

In the first aggregate example the resulting data was grouped by letter, but in the second window example, we preserved our rows.

SELECT
  letter,
  SUM(number) AS aggregate
FROM
  (
    SELECT
      'A' AS letter,
      2 AS number
    UNION ALL
(    SELECT
      'A' AS letter,
      3 AS number)
    UNION ALL
(    SELECT
      'C' AS letter,
      6 AS number)
  ) AS table_3
GROUP BY
  letter

Why use window functions?

Window functions are very powerful, and once you've gotten your head around how to use them, you'll be surprised at they allow you to do. Some common use cases are:

SELECT
  letter,
  SUM(number) AS aggregate
FROM
  (
    SELECT
      'A' AS letter,
      2 AS number
    UNION ALL
(    SELECT
      'A' AS letter,
      3 AS number)
    UNION ALL
(    SELECT
      'C' AS letter,
      6 AS number)
  ) AS table_3
GROUP BY
  letter
  • Running/Cumulative Total
  • Moving Average
  • Rank rows by custom criteria and groupings
  • Finding the year-over-year % Change‍

‍where the PARTITION BY denotes how to GROUP rows into partitions, ORDER BY how to order the rows in those partitions, and ROWS which rows to consider in those ordered partitions.

Window Functions

In general, window functions can be grouped into 3 types:

window_function_name ([arguments]) OVER (
   [PARTITION BY partition_expression] 
   [ORDER BY expression ASC | DESC] 
   [ROWS frame_clause])
  • Navigation functions: Return the value given a specific location criteria (e.g. first_value)
  • Numbering functions: Assign a number (e.g. rank) to each row based on their position in the specified window
  • Analytic functions: Perform a calculation on a set of values (e.g. sum)

Navigation functions

  • FIRST_VALUE : FIRST_VALUE (value_expression [{RESPECT | IGNORE} NULLS]) -> Returns the value_expression for the first row in the current window frame.
  • LAST_VALUE : LAST_VALUE (value_expression [{RESPECT | IGNORE} NULLS]) -> Returns the value_expression for the last row in the current window frame.
  • NTH_VALUE : NTH_VALUE (value_expression, constant_integer_expression [{RESPECT | IGNORE} NULLS]) -> Returns the value_expression for the Nth row of the current window frame.
  • LEAD : LEAD (value_expression[, offset [, default_expression]]) -> Returns the value_expression on the subsequent row.
  • LAG : LAG (value_expression[, offset [, default_expression]]) -> Returns the value_expression on the preceding row.
  • PERCENTILE_CONT : PERCENTILE_CONT (value_expression, percentile [{RESPECT | IGNORE} NULLS]) -> Returns the percentile of the value_expression with linear interpolation (continuous scale).
  • PERCENTILE_DISC : PERCENTILE_DIST (value_expression, percentile [{RESPECT | IGNORE} NULLS]) -> Returns the percentile of the value_expression (discrete scale).

For these functions we'll use the following demo data:

SELECT DISTINCT
  player,
  season,
  FIRST_VALUE(points) OVER (PARTITION BY player ORDER BY season ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS first_season,
  LAST_VALUE(points) OVER (PARTITION BY player ORDER BY season ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS last_season,
  (100 * ((LAST_VALUE(points) OVER (PARTITION BY player ORDER BY season ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) - FIRST_VALUE(points) OVER (PARTITION BY player ORDER BY season ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)) / FIRST_VALUE(points) OVER (PARTITION BY player ORDER BY season ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING))) AS per_change
FROM
  top_scorers
playerseasonfirst_seasonlast_seasonper_change
Damian Lillard201920671978-4.31
Damian Lillard202020671978-4.31
Devin Booker2019170018639.59
Devin Booker2020170018639.59
James Harden201928182335-17.14
James Harden202028182335-17.14
Kemba Walker201921021145-45.53
Kemba Walker202021021145-45.53
Paul George201919781033-47.78
Paul George202019781033-47.78

‍How to find a year-over-year change

seasonpoints_rankplayerpoints
20191James Harden2818
20192Kemba Walker2102
20193Damian Lillard2067
20201James Harden2335
20202Damian Lillard1978
20203Devin Booker1863

We used FIRST_VALUE and LAST_VALUE to find the scores for each player in the earliest and most recent seasons of data. Then we computed the percent difference using:

100 * ((new value - old value) / old value) per_difference

Numbering functions

  • RANK : RANK() -> Returns the rank of each row in the ordered partition (starts at 1).
  • DENSE_RANK : DENSE_RANK() -> Returns the rank, but values of the same value get the same rank (starts at 1).
  • PERCENT_RANK : PERCENT_RANK() -> Returns the percentile rank of a row.
  • CUME_DIST : CUME_DIST() -> Returns the relative rank of a row .
  • NTILE: NTILE(constant_integer_expression) -> Returns the bucket number after dividing each partition into constant_integer_expression buckets.
  • ROW_NUMBER : ROW_NUMBER() -> Returns the sequential row number for each ordered partition.

‍⚠️ Numbering functions are not allowed [ROWS frame_clause]

How to get top 3 results for each group?

seasonplayerpointsrunning_total_points
2019Damian Lillard20672067
2020Damian Lillard19784045
2019Devin Booker17001700
2020Devin Booker18633563
2019James Harden28182818
2020James Harden23355153
2019Kemba Walker21022102
2020Kemba Walker11453247
2019Paul George19781978
2020Paul George10333011

In this example, we used RANK to rank each player by points over each season. Then we used a subquery to then return only the top 3 ranked players for each season.

SELECT
  *
FROM
  (
    SELECT
      season,
      RANK() OVER (PARTITION BY season ORDER BY points DESC) AS points_rank,
      player,
      points
    FROM
      top_scorers
  ) AS table_1
WHERE
  (points_rank <= 3)

Aggregate functions

Aggregate functions are available outside of window functions, but can be additionally applied over a specified window.

  • ANY_VALUE : ANY_VALUE(expression)  [OVER (...)] -> Returns expression for some row chosen from the group. Basically a random selection from an expression.
  • ARRAY_AGG : Returns an array of expression values.
  • AVG : AVG([DISTINCT] expression)  [OVER (...)] -> Returns the average of the non-NULL input values.
  • CORR : CORR(X1, X2)  [OVER (...)] -> Returns the Pearson coefficient of correlation for a set of number pairs.
  • COUNT : COUNT([DISTINCT] expression] [OVER (...)] -> Returns the number of [distinct] elements in expression
  • COUNTIF : COUNTIF(expression)  [OVER (...)] -> Returns the count of True values for expression
  • COVAR_POP : COVAR_POP(X1, X2)  [OVER (...)] -> Returns the population covariance of a set of numbers
  • COVAR_SAMP : COVAR_SAMP(X1, X2)  [OVER (...)] -> Returns the sample covariance of a set of numbers
  • MAX : MAX(expression)  [OVER (...)] -> Returns the maximum non-NULL value of expression.
  • MIN: MIN(expression)  [OVER (...)] -> Returns the minimum non-NULL value of expression.
  • ST_CUSTERDBSCAN : ST_CLUSTERDBSCAN(geography_column, epsilon, minimum_geographies) OVER (...) -> Performs DBSCAN clustering on a column of geographies
  • STDEV_POP : STDDEV_POP([DISTINCT] expression)  [OVER (...)] -> Returns population standard deviation of values
  • STDEV_SAMP : STDDEV_SAMP([DISTINCT] expression)  [OVER (...)] -> Returns sample standard deviation of values
  • STRING_AGG : STRING_AGG([DISTINCT] expression [, delimiter]  [ORDER BY key [{ASC|DESC}] [, ... ]]  [LIMIT n]) [OVER (...)] -> Returns a value obtained by concatenating all the non-null values
  • SUM : SUM([DISTINCT] expression)  [OVER (...)] -> Returns the sum of all non-null values
  • VAR_POP : VAR_POP([DISTINCT] expression)  [OVER (...)] -> Returns the population variance of results
  • VAR_SAMP : VAR_SAMP([DISTINCT] expression)  [OVER (...)] -> Returns the sample variance of results

‍How to find a running total?

datenumbermoving_avg
2020-01-0111
2020-01-0221.5
2020-01-0332
2020-01-0442.5
2020-01-0553
2020-01-0663.5
2020-01-0774
2020-01-0885
2020-01-0996
2020-01-10107

To find the running total simply use SUM with an OVER clause where you specify your groupings (PARTITION BY), and the order in which to add them (ORDER BY).

How to find a moving average?

datenumbermoving_avg
2020-01-0111
2020-01-0221.5
2020-01-0332
2020-01-0442.5
2020-01-0553
2020-01-0663.5
2020-01-0774
2020-01-0885
2020-01-0996
2020-01-10107

To set the 7 day moving average window we can use the ROWS BETWEEN 6 PRECEDING AND CURRENT ROW in our OVER clause.

How to find the most popular item a group?

itemcategoryqtymost_popular
applefruit1pear
pearfruit3pear
green beansvegetable4brussel sprouts
brussel sproutsvegetable5brussel sprouts
ARRAY_AGG([DISTINCT] expression [{IGNORE|RESPECT} NULLS]
  [ORDER BY key [{ASC|DESC}] [, ... ]]  [LIMIT n]
) [OVER (...)]
SELECT
  season,
  player,
  points,
  SUM(top_scorers.points) OVER (PARTITION BY player ORDER BY season ASC) AS running_total_points
FROM
  top_scorers
SELECT
  date,
  number,
  AVG(number) OVER (ORDER BY date ASC ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg
FROM
  (
    SELECT
      date,
      number
    FROM
      UNNEST(GENERATE_DATE_ARRAY('2020-01-01', '2020-02-01')) AS date
      CROSS JOIN UNNEST(GENERATE_ARRAY(1, 30)) AS number
  ) AS table_1
LIMIT
  10
SELECT
  item,
  category,
  qty,
  LAST_VALUE(item) OVER (PARTITION BY category ORDER BY qty DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS most_popular
FROM
  (
    SELECT
      'apple' AS item,
      'fruit' AS category,
      1 AS qty
    UNION ALL
(    SELECT
      'pear' AS item,
      'fruit' AS category,
      3 AS qty)
    UNION ALL
(    SELECT
      'green beans' AS item,
      'vegetable' AS category,
      4 AS qty)
    UNION ALL
(    SELECT
      'brussel sprouts' AS item,
      'vegetable' AS category,
      5 AS qty)
  ) AS table_4

Got a CSV?
See it differently in <2 mins

Get started for FREEWatch our CEO do it