窗口函数

1.窗口函数介绍

窗口函数是一种 SQL 函数,其中输入值取自 SELECT 语句结果集中一行或多行的“窗口”。

窗口函数与其他 SQL 函数的区别在于存在 OVER 子句。如果一个函数有一个 OVER 子句,那么它就是一个窗口函数。如果它缺少 OVER 子句,则它是一个普通的聚合函数或标量函数。窗口函数也可能在函数和 OVER 子句之间有一个 FILTER 子句。

窗口函数的语法是这样的:

窗口函数调用:

window-func ( expr ) filter-clause OVER window-name window-defn , *

表达式:

过滤器子句:

FILTER ( WHERE expr )

窗口定义:

( base-window-name PARTITION BY expr , ORDER BY ordering-term , frame-spec )

框架规格:

GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING RANGE ROWS UNBOUNDED PRECEDING expr PRECEDING CURRENT ROW expr PRECEDING CURRENT ROW expr FOLLOWING expr PRECEDING CURRENT ROW expr FOLLOWING EXCLUDE CURRENT ROW EXCLUDE GROUP EXCLUDE TIES EXCLUDE NO OTHERS

订购条款:

与普通函数不同,窗口函数不能使用 DISTINCT 关键字。此外,窗口函数可能只出现在结果集中和 SELECT 语句的 ORDER BY 子句中。

窗口函数有两种: 聚合窗口函数内置窗口函数每个聚合窗口函数也可以像普通聚合函数一样工作,只需省略 OVER 和 FILTER 子句即可。此外,通过添加适当的 OVER 子句,SQLite 的所有内置 聚合函数都可以用作聚合窗口函数。应用程序可以使用sqlite3_create_window_function()接口注册新的聚合窗口函数。然而,内置窗口函数需要在查询规划器中进行特殊情况处理,因此应用程序无法添加展示内置窗口函数中发现的异常属性的新窗口函数。

下面是一个使用内置 row_number() 窗口函数的示例:

CREATE TABLE t0(x INTEGER PRIMARY KEY, y TEXT);
INSERT INTO t0 VALUES (1, 'aaa'), (2, 'ccc'), (3, 'bbb');

-- The following SELECT statement returns:
-- 
--   x | y | row_number
-----------------------
--   1 | aaa | 1         
--   2 | ccc | 3         
--   3 | bbb | 2         
-- 
SELECT x, y, row_number() OVER (ORDER BY y) AS row_number FROM t0 ORDER BY x;

row_number() 窗口函数按照window-defn 中“ORDER BY”子句的顺序为每一行分配连续的整数 (在本例中为“ORDER BY y”)。请注意,这不会影响从整个查询返回结果的顺序。最终输出的顺序仍然由附加到 SELECT 语句的 ORDER BY 子句控制(在本例中为“ORDER BY x”)。

命名的 window-defn子句也可以添加到使用 WINDOW 子句的 SELECT 语句,然后在窗口函数调用中通过名称引用。例如,以下 SELECT 语句包含两个命名的 window-defs子句,“win1”和“win2”:

SELECT x, y, row_number() OVER win1, rank() OVER win2 
FROM t0 
WINDOW win1 AS (ORDER BY y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
       win2 AS (PARTITION BY y ORDER BY x)
ORDER BY x;

WINDOW 子句(如果存在)位于任何 HAVING 子句之后和任何 ORDER BY 之前。

2.聚合窗口函数

本节中的示例均假定数据库填充如下:

CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES   (1, 'A', 'one'  ),
                        (2, 'B', 'two'  ),
                        (3, 'C', 'three'),
                        (4, 'D', 'one'  ),
                        (5, 'E', 'two'  ),
                        (6, 'F', 'three'),
                        (7, 'G', 'one'  );

聚合窗口函数类似于 普通的聚合函数,除了将它添加到查询中不会改变返回的行数。相反,对于每一行,聚合窗口函数的结果就好像相应的聚合在 OVER 子句指定的“窗口框架”中的所有行上运行一样。

-- The following SELECT statement returns:
-- 
--   a | b | group_concat
-------------------------
--   1 | A | A.B         
--   2 | B | A.B.C       
--   3 | C | B.C.D       
--   4 | D | C.D.E       
--   5 | E | D.E.F       
--   6 | F | E.F.G       
--   7 | G | F.G         
-- 
SELECT a, b, group_concat(b, '.') OVER (
  ORDER BY a ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS group_concat FROM t1;

在上面的示例中,窗口框架由前一行(“1 PRECEDING”)和下一行(“1 FOLLOWING”)之间的所有行组成,包括在内,其中行根据window-defn中的 ORDER BY 子句 排序(在本例中为“ORDER BY a”)。例如,具有 (a=3) 的行的框架由行 (2, 'B', 'two')、(3, 'C', 'three') 和 (4, 'D', 'one '). 因此,该行的 group_concat(b, '.') 的结果是 'BCD'。

SQLite 的所有聚合函数都可以用作聚合窗口函数。也可以 创建用户定义的聚合窗口函数

2.1. PARTION BY 子句

为了计算窗口函数,查询的结果集被分成一个或多个“分区”。分区由window-defn中 PARTITION BY 子句的所有项具有相同值的所有行组成如果没有 PARTITION BY 子句,则查询的整个结果集是单个分区。窗口函数处理是针对每个分区单独执行的。

例如:

-- The following SELECT statement returns:
-- 
--   c     | a | b | group_concat
---------------------------------
--   one   | 1 | A | A.D.G       
--   one   | 4 | D | D.G         
--   one   | 7 | G | G           
--   three | 3 | C | C.F         
--   three | 6 | F | F           
--   two   | 2 | B | B.E         
--   two   | 5 | E | E           
-- 
SELECT c, a, b, group_concat(b, '.') OVER (
  PARTITION BY c ORDER BY a RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS group_concat
FROM t1 ORDER BY c, a;

在上面的查询中,“PARTITION BY c”子句将结果集分成三个分区。第一个分区有三行 c=='one'。第二个分区有两行 c=='three',第三个分区有两行 c=='two'。

在上面的示例中,每个分区的所有行在最终输出中组合在一起。这是因为 PARTITION BY 子句是整个查询中 ORDER BY 子句的前缀。但事实并非如此。一个分区可以由随机散布在结果集中的行组成。例如:

-- The following SELECT statement returns:
-- 
--   c     | a | b | group_concat
---------------------------------
--   one   | 1 | A | A.D.G       
--   two   | 2 | B | B.E         
--   three | 3 | C | C.F         
--   one   | 4 | D | D.G         
--   two   | 5 | E | E           
--   three | 6 | F | F           
--   one   | 7 | G | G           
-- 
SELECT c, a, b, group_concat(b, '.') OVER (
  PARTITION BY c ORDER BY a RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS group_concat
FROM t1 ORDER BY a;

2.2. 车架规格

frame-spec确定聚合窗口函数读取哪些输出行frame-spec四部分组成:

  • 帧类型 - ROWS、RANGE 或 GROUPS,
  • 起始帧边界,
  • 结束帧边界,
  • 一个 EXCLUDE 子句。

以下是语法细节:

框架规格:

GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING RANGE ROWS UNBOUNDED PRECEDING expr PRECEDING CURRENT ROW expr PRECEDING CURRENT ROW expr FOLLOWING expr PRECEDING CURRENT ROW expr FOLLOWING EXCLUDE CURRENT ROW EXCLUDE GROUP EXCLUDE TIES EXCLUDE NO OTHERS

表达式:

结束帧边界可以省略(如果围绕起始帧边界的 BETWEEN 和 AND 关键字也被省略),在这种情况下结束帧边界默认为 CURRENT ROW。

如果帧类型是 RANGE 或 GROUPS,则所有 ORDER BY 表达式具有相同值的行被视为“对等”。或者,如果没有 ORDER BY 项,则所有行都是对等的。同行总是在同一个框架内。

默认的框架规范是:

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS

默认值意味着聚合窗口函数读取从分区开始到并包括当前行及其对等行的所有行。这意味着对于所有 ORDER BY 表达式具有相同值的行对于窗口函数的结果也将具有相同的值(因为窗口框架是相同的)。例如:

-- The following SELECT statement returns:
-- 
--   a | b | c | group_concat
-----------------------------
--   1 | A | one   | A.D.G       
--   2 | B | two   | A.D.G.C.F.B.E
--   3 | C | three | A.D.G.C.F   
--   4 | D | one   | A.D.G       
--   5 | E | two   | A.D.G.C.F.B.E
--   6 | F | three | A.D.G.C.F   
--   7 | G | one   | A.D.G       
-- 
SELECT a, b, c, 
       group_concat(b, '.') OVER (ORDER BY c) AS group_concat 
FROM t1 ORDER BY a;

2.2.1. 帧类型

共有三种帧类型:ROWS、GROUPS 和 RANGE。帧类型决定了如何测量帧的开始和结束边界。

  • ROWS:ROWS 帧类型意味着帧的开始和结束边界是通过计算相对于当前行的各个行来确定的。

  • GROUPS:GROUPS 帧类型意味着开始和结束边界是通过计算相对于当前组的“组”来确定的。“组”是一组行,对于窗口 ORDER BY 子句的所有项,它们都具有等效值。(“等效”意味着比较两个值时IS 运算符为真。)换句话说,一个组由一行的所有对等体组成。

  • RANGE:RANGE 帧类型要求窗口的 ORDER BY 子句只有一个术语。将该术语称为“X”。对于 RANGE 帧类型,帧的元素是通过计算分区中所有行的表达式 X 的值并将 X 的值在当前行的 X 值的特定范围内的那些行来确定的. 有关详细信息,请参阅下面“ <expr> PRECEDING ”边界规范中的描述。

ROWS 和 GROUPS 帧类型的相似之处在于它们都通过相对于当前行的计数来确定帧的范围。区别在于 ROWS 计算单个行,而 GROUPS 计算对等组。RANGE 帧类型不同。RANGE 帧类型通过查找相对于当前行的某个值范围内的表达式值来确定帧的范围。

2.2.2. 框架边界

有五种方式来描述开始和结束帧边界:

  1. UNBOUNDED PRECEDING
    帧边界是分区中的第一行。

  2. <expr> PRECEDING
    <expr> 必须是非负常量数值表达式。边界是比当前行早 <expr> 个“单位”的行。这里“单位”的含义取决于帧类型:

    • ROWS → 帧边界是当前行之前 <expr> 行的行,或者如果当前行之前的行少于 <expr> 行,则为分区的第一行。<expr> 必须是整数。

    • GROUPS → “组”是一组对等行 - 对于 ORDER BY 子句中的每个术语都具有相同值的行。帧边界是包含当前行的组之前 <expr> 组的组,或者如果当前行之前的组少于 <expr> 组,则为分区的第一组。对于帧的起始边界,使用组的第一行,对于帧的结束边界,使用组的最后一行。<expr> 必须是整数。

    • RANGE → 对于这种形式, 窗口定义的 ORDER BY 子句必须有一个单独的术语。将该 ORDER BY 术语称为“X”。Xi为分区中第 i 行的 X 表达式的值,令Xc为当前行的 X 的值。非正式地,RANGE 边界是 X i在 X c的 <expr> 内的第一行更确切地说:

      1. 如果 X i或 X c是非数字,则边界是表达式“X i IS X c ”为真的第一行。
      2. 否则,如果 ORDER BY 是 ASC,则边界是 X i >=X c -<expr> 的第一行。
      3. 否则,如果 ORDER BY 是 DESC,则边界是 X i <=X c -<expr> 的第一行。
      对于这种形式,<expr> 不必是整数。只要它是常量且非负的,它就可以计算为实数。
    边界描述“0 PRECEDING”始终与“CURRENT ROW”含义相同。
  3. 当前
    行 当前行。对于 RANGE 和 GROUPS 帧类型,当前行的对等点也包含在帧中,除非 EXCLUDE 子句明确排除。无论 CURRENT ROW 是用作起始帧边界还是结束帧边界,都是如此。

  4. <expr> FOLLOWING
    这与“<expr> PRECEDING”相同,只是边界是在当前行之后而不是当前行之前的 <expr> 个单位。

  5. UNBOUNDED FOLLOWING
    帧边界是分区中的最后一行。

在上述列表中,结束帧边界可能不会采用比起始帧边界更高的形式。

在以下示例中,每行的窗口框架包含从当前行到集合末尾的所有行,其中行根据“ORDER BY a”排序。

-- The following SELECT statement returns:
-- 
--   c     | a | b | group_concat
---------------------------------
--   one   | 1 | A | A.D.G.C.F.B.E
--   one   | 4 | D | D.G.C.F.B.E 
--   one   | 7 | G | G.C.F.B.E   
--   three | 3 | C | C.F.B.E     
--   three | 6 | F | F.B.E       
--   two   | 2 | B | B.E         
--   two   | 5 | E | E           
-- 
SELECT c, a, b, group_concat(b, '.') OVER (
  ORDER BY c, a ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS group_concat
FROM t1 ORDER BY c, a;

2.2.3. 排除条款

可选的 EXCLUDE 子句可以采用以下四种形式中的任何一种:

  • 不排除其他人:这是默认设置。在这种情况下,没有行被排除在由其起始和结束框架边界定义的窗口框架之外。

  • EXCLUDE CURRENT ROW:在这种情况下,当前行被排除在窗口框架之外。当前行的对等点保留在 GROUPS 和 RANGE 帧类型的帧中。

  • EXCLUDE GROUP:在这种情况下,当前行和与当前行对等的所有其他行都被排除在框架之外。处理 EXCLUDE 子句时,所有具有相同 ORDER BY 值的行,或者分区中没有 ORDER BY 子句的所有行,都被视为对等行,即使帧类型是 ROWS。

  • EXCLUDE TIES:在这种情况下,当前行是框架的一部分,但当前行的对等点被排除在外。

以下示例演示了各种形式的 EXCLUDE 子句的效果:

-- The following SELECT statement returns:
-- 
--   c    | a | b | no_others     | current_row | grp       | ties
--  one   | 1 | A | A.D.G         | D.G         |           | A
--  one   | 4 | D | A.D.G         | A.G         |           | D
--  one   | 7 | G | A.D.G         | A.D         |           | G
--  three | 3 | C | A.D.G.C.F     | A.D.G.F     | A.D.G     | A.D.G.C
--  three | 6 | F | A.D.G.C.F     | A.D.G.C     | A.D.G     | A.D.G.F
--  two   | 2 | B | A.D.G.C.F.B.E | A.D.G.C.F.E | A.D.G.C.F | A.D.G.C.F.B
--  two   | 5 | E | A.D.G.C.F.B.E | A.D.G.C.F.B | A.D.G.C.F | A.D.G.C.F.E
-- 
SELECT c, a, b,
  group_concat(b, '.') OVER (
    ORDER BY c GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS
  ) AS no_others,
  group_concat(b, '.') OVER (
    ORDER BY c GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW
  ) AS current_row,
  group_concat(b, '.') OVER (
    ORDER BY c GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP
  ) AS grp,
  group_concat(b, '.') OVER (
    ORDER BY c GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES
  ) AS ties
FROM t1 ORDER BY c, a;

2.3. FILTER 子句

过滤器子句:

FILTER ( WHERE expr )

表达式:

如果提供了 FILTER 子句,则只有expr为真的行才会包含在窗口框架中。聚合窗口仍会为每一行返回一个值,但 FILTER 表达式的计算结果不是 true 的那些值不会包含在任何行的窗口框架中。例如:

-- The following SELECT statement returns:
-- 
--   c     | a | b | group_concat
---------------------------------
--   one   | 1 | A | A           
--   two   | 2 | B | A           
--   three | 3 | C | A.C         
--   one   | 4 | D | A.C.D       
--   two   | 5 | E | A.C.D       
--   three | 6 | F | A.C.D.F     
--   one   | 7 | G | A.C.D.F.G   
-- 
SELECT c, a, b, group_concat(b, '.') FILTER (WHERE c!='two') OVER (
  ORDER BY a
) AS group_concat
FROM t1 ORDER BY a;

3.内置窗口函数

除了聚合窗口函数外,SQLite 还具有一组基于PostgreSQL 支持的内置窗口函数

内置窗口函数以与聚合窗口函数相同的方式遵守任何 PARTITION BY 子句 - 每个选定的行都分配给一个分区,并且每个分区单独处理。下面描述了任何 ORDER BY 子句影响每个内置窗口函数的方式。一些窗口函数(rank()、dense_rank()、percent_rank() 和 ntile())使用“对等组”的概念(同一分区内的行对所有 ORDER BY 表达式具有相同的值)。在这些情况下,框架规范是否指定 ROWS、GROUPS 或 RANGE并不重要。出于内置窗口函数处理的目的,所有 ORDER BY 表达式具有相同值的行被视为对等行,而不管帧类型如何。

大多数内置窗口函数忽略 frame-spec,first_value()、last_value() 和 nth_value() 除外。将 FILTER 子句指定为内置窗口函数调用的一部分是语法错误。

SQLite 支持以下 11 个内置窗口函数:

row_number()

The number of the row within the current partition. Rows are numbered starting from 1 in the order defined by the ORDER BY clause in the window definition, or in arbitrary order otherwise.

rank()

The row_number() of the first peer in each group - the rank of the current row with gaps. If there is no ORDER BY clause, then all rows are considered peers and this function always returns 1.

dense_rank()

The number of the current row's peer group within its partition - the rank of the current row without gaps. Partitions are numbered starting from 1 in the order defined by the ORDER BY clause in the window definition. If there is no ORDER BY clause, then all rows are considered peers and this function always returns 1.

percent_rank()

Despite the name, this function always returns a value between 0.0 and 1.0 equal to (rank - 1)/(partition-rows - 1), where rank is the value returned by built-in window function rank() and partition-rows is the total number of rows in the partition. If the partition contains only one row, this function returns 0.0.

cume_dist()

The cumulative distribution. Calculated as row-number/partition-rows, where row-number is the value returned by row_number() for the last peer in the group and partition-rows the number of rows in the partition.

ntile(N)

Argument N is handled as an integer. This function divides the partition into N groups as evenly as possible and assigns an integer between 1 and N to each group, in the order defined by the ORDER BY clause, or in arbitrary order otherwise. If necessary, larger groups occur first. This function returns the integer value assigned to the group that the current row is a part of.

lag(expr)
lag(expr, offset)
lag(expr, offset, default)

The first form of the lag() function returns the result of evaluating expression expr against the previous row in the partition. Or, if there is no previous row (because the current row is the first), NULL.

If the offset argument is provided, then it must be a non-negative integer. In this case the value returned is the result of evaluating expr against the row offset rows before the current row within the partition. If offset is 0, then expr is evaluated against the current row. If there is no row offset rows before the current row, NULL is returned.

If default is also provided, then it is returned instead of NULL if the row identified by offset does not exist.

lead(expr)
lead(expr, offset)
lead(expr, offset, default)

The first form of the lead() function returns the result of evaluating expression expr against the next row in the partition. Or, if there is no next row (because the current row is the last), NULL.

If the offset argument is provided, then it must be a non-negative integer. In this case the value returned is the result of evaluating expr against the row offset rows after the current row within the partition. If offset is 0, then expr is evaluated against the current row. If there is no row offset rows after the current row, NULL is returned.

If default is also provided, then it is returned instead of NULL if the row identified by offset does not exist.

first_value(expr)

This built-in window function calculates the window frame for each row in the same way as an aggregate window function. It returns the value of expr evaluated against the first row in the window frame for each row.

last_value(expr)

This built-in window function calculates the window frame for each row in the same way as an aggregate window function. It returns the value of expr evaluated against the last row in the window frame for each row.

nth_value(expr, N)

This built-in window function calculates the window frame for each row in the same way as an aggregate window function. It returns the value of expr evaluated against the row N of the window frame. Rows are numbered within the window frame starting from 1 in the order defined by the ORDER BY clause if one is present, or in arbitrary order otherwise. If there is no Nth row in the partition, then NULL is returned.

本节中的示例均假定以下数据:

CREATE TABLE t2(a, b);
INSERT INTO t2 VALUES('a', 'one'), 
                     ('a', 'two'), 
                     ('a', 'three'), 
                     ('b', 'four'), 
                     ('c', 'five'), 
                     ('c', 'six');

以下示例说明了五个排名函数的行为 - row_number()、rank()、dense_rank()、percent_rank() 和 cume_dist()。

-- The following SELECT statement returns:
-- 
--   a | row_number | rank | dense_rank | percent_rank | cume_dist
------------------------------------------------------------------
--   a |          1 |    1 |          1 |          0.0 |       0.5
--   a |          2 |    1 |          1 |          0.0 |       0.5
--   a |          3 |    1 |          1 |          0.0 |       0.5
--   b |          4 |    4 |          2 |          0.6 |       0.66
--   c |          5 |    5 |          3 |          0.8 |       1.0
--   c |          6 |    5 |          3 |          0.8 |       1.0
-- 
SELECT a                        AS a,
       row_number() OVER win    AS row_number,
       rank() OVER win          AS rank,
       dense_rank() OVER win    AS dense_rank,
       percent_rank() OVER win  AS percent_rank,
       cume_dist() OVER win     AS cume_dist
FROM t2
WINDOW win AS (ORDER BY a);

下面的示例使用 ntile() 将六行分成两组(ntile(2) 调用)和四组(ntile(4) 调用)。对于 ntile(2),每个组分配了三行。对于 ntile(4),有两组两个和两组一个。较大的两组首先出现。

-- The following SELECT statement returns:
-- 
--   a | b     | ntile_2 | ntile_4
----------------------------------
--   a | one   |       1 |       1
--   a | two   |       1 |       1
--   a | three |       1 |       2
--   b | four  |       2 |       2
--   c | five  |       2 |       3
--   c | six   |       2 |       4
-- 
SELECT a                        AS a,
       b                        AS b,
       ntile(2) OVER win        AS ntile_2,
       ntile(4) OVER win        AS ntile_4
FROM t2
WINDOW win AS (ORDER BY a);

下一个示例演示了 lag()、lead()、first_value()、last_value() 和 nth_value()。frame-spec被 lag() 和 lead() 忽略,但被 first_value()、last_value() 和 nth_value() 尊重。

-- The following SELECT statement returns:
-- 
--   b | lead | lag  | first_value | last_value | nth_value_3
-------------------------------------------------------------
--   A | C    | NULL | A           | A          | NULL       
--   B | D    | A    | A           | B          | NULL       
--   C | E    | B    | A           | C          | C          
--   D | F    | C    | A           | D          | C          
--   E | G    | D    | A           | E          | C          
--   F | n/a  | E    | A           | F          | C          
--   G | n/a  | F    | A           | G          | C          
-- 
SELECT b                          AS b,
       lead(b, 2, 'n/a') OVER win AS lead,
       lag(b) OVER win            AS lag,
       first_value(b) OVER win    AS first_value,
       last_value(b) OVER win     AS last_value,
       nth_value(b, 3) OVER win   AS nth_value_3
FROM t1
WINDOW win AS (ORDER BY b ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

4.窗口链接

窗口链接是一种简写形式,允许根据另一个窗口定义一个窗口。具体来说,速记允许新窗口隐式复制基本窗口的 PARTITION BY 和可选的 ORDER BY 子句。例如,在下面:

SELECT group_concat(b, '.') OVER (
  win ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
FROM t1
WINDOW win AS (PARTITION BY a ORDER BY c)

group_concat() 函数使用的窗口等效于“PARTITION BY a ORDER BY c ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW”。为了使用窗口链接,以下所有条件都必须为真:

  • 新窗口定义不得包含 PARTITION BY 子句。PARTITION BY 子句(如果有的话)必须由基本窗口规范提供。

  • 如果基窗口有一个 ORDER BY 子句,它被复制到新窗口中。在这种情况下,新窗口不得指定 ORDER BY 子句。如果基本窗口没有 ORDER BY 子句,则可以将其指定为新窗口定义的一部分。

  • 基本窗口可能不指定框架规范。框架规范只能在新的窗口规范中给出。

下面的两个 SQL 片段相似,但不完全等同,因为如果窗口“win”的定义包含框架规范,则后者将失败。

SELECT group_concat(b, '.') OVER win ...
SELECT group_concat(b, '.') OVER (win) ...

5.用户自定义聚合窗口函数

可以使用 sqlite3_create_window_function () API 创建用户定义的聚合窗口函数。实现聚合窗口函数与普通聚合函数非常相似。任何用户定义的聚合窗口函数也可以用作普通聚合。要实现用户定义的聚合窗口函数,应用程序必须提供四个回调函数:

打回来描述
x步 窗口聚合和遗留聚合函数实现都需要此方法。它被调用以向当前窗口添加一行。与要添加的行对应的函数参数(如果有)将传递给 xStep 的实现。
x决赛 窗口聚合和遗留聚合函数实现都需要此方法。调用它以返回聚合的当前值(由当前窗口的内容确定),并释放之前调用 xStep 分配的任何资源。
x值 此方法仅需要窗口聚合函数,而不是遗留聚合函数实现。它被调用以返回聚合的当前值。与 xFinal 不同,该实现不应删除任何上下文。
x逆 仅窗口聚合函数需要此方法,而遗留聚合函数实现则不需要。它被调用以从当前窗口中删除 xStep 最旧的当前聚合结果。函数参数(如果有)是为要删除的行传递给 xStep 的参数。

下面的 C 代码实现了一个名为 sumint() 的简单窗口聚合函数。这与内置的 sum() 函数的工作方式相同,只是如果传递的参数不是整数值,它会抛出异常。

/*
** xStep for sumint().
**
** Add the value of the argument to the aggregate context (an integer).
*/
static void sumintStep(
  sqlite3_context *ctx, 
  int nArg, 
  sqlite3_value *apArg[]
){
  sqlite3_int64 *pInt;

  assert( nArg==1 );
  if( sqlite3_value_type(apArg[0])!=SQLITE_INTEGER ){
    sqlite3_result_error(ctx, "invalid argument", -1);
    return;
  }
  pInt = (sqlite3_int64*)sqlite3_aggregate_context(ctx, sizeof(sqlite3_int64));
  if( pInt ){
    *pInt += sqlite3_value_int64(apArg[0]);
  }
}

/*
** xInverse for sumint().
**
** This does the opposite of xStep() - subtracts the value of the argument
** from the current context value. The error checking can be omitted from
** this function, as it is only ever called after xStep() (so the aggregate
** context has already been allocated) and with a value that has already
** been passed to xStep() without error (so it must be an integer).
*/
static void sumintInverse(
  sqlite3_context *ctx, 
  int nArg, 
  sqlite3_value *apArg[]
){
  sqlite3_int64 *pInt;
  assert( sqlite3_value_type(apArg[0])==SQLITE_INTEGER );
  pInt = (sqlite3_int64*)sqlite3_aggregate_context(ctx, sizeof(sqlite3_int64));
  *pInt -= sqlite3_value_int64(apArg[0]);
}

/*
** xFinal for sumint().
**
** Return the current value of the aggregate window function. Because
** this implementation does not allocate any resources beyond the buffer
** returned by sqlite3_aggregate_context, which is automatically freed
** by the system, there are no resources to free. And so this method is
** identical to xValue().
*/
static void sumintFinal(sqlite3_context *ctx){
  sqlite3_int64 res = 0;
  sqlite3_int64 *pInt;
  pInt = (sqlite3_int64*)sqlite3_aggregate_context(ctx, 0);
  if( pInt ) res = *pInt;
  sqlite3_result_int64(ctx, res);
}

/*
** xValue for sumint().
**
** Return the current value of the aggregate window function.
*/
static void sumintValue(sqlite3_context *ctx){
  sqlite3_int64 res = 0;
  sqlite3_int64 *pInt;
  pInt = (sqlite3_int64*)sqlite3_aggregate_context(ctx, 0);
  if( pInt ) res = *pInt;
  sqlite3_result_int64(ctx, res);
}

/*
** Register sumint() window aggregate with database handle db. 
*/
int register_sumint(sqlite3 *db){
  return sqlite3_create_window_function(db, "sumint", 1, SQLITE_UTF8, 0,
      sumintStep, sumintFinal, sumintValue, sumintInverse, 0
  );
}

以下示例使用由上述 C 代码实现的 sumint() 函数。对于每一行,窗口由前一行(如果有)、当前行和下一行(同样,如果有)组成:

CREATE TABLE t3(x, y);
INSERT INTO t3 VALUES('a', 4),
                     ('b', 5),
                     ('c', 3),
                     ('d', 8),
                     ('e', 1);

-- Assuming the database is populated using the above script, the 
-- following SELECT statement returns:
-- 
--   x | sum_y
--------------
--   a | 9    
--   b | 12   
--   c | 16   
--   d | 12   
--   e | 9    
-- 
SELECT x, sumint(y) OVER (
  ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS sum_y
FROM t3 ORDER BY x;

在处理上面的查询时,SQLite 调用 sumint 回调如下:

  1. xStep(4) - 将“4”添加到当前窗口。
  2. xStep(5) - 将“5”添加到当前窗口。
  3. xValue() - 调用 xValue() 以获得具有 (x='a') 的行的 sumint() 值。该窗口当前包含值 4 和 5,因此结果为 9。
  4. xStep(3) - 将“3”添加到当前窗口。
  5. xValue() - 调用 xValue() 以获得带有 (x='b') 的行的 sumint() 值。该窗口当前包含值 4、5 和 3,因此结果为 12。
  6. xInverse(4) - 从窗口中删除“4”。
  7. xStep(8) - 将“8”添加到当前窗口。该窗口现在由值 5、3 和 8 组成。
  8. xValue() - 调用以获取具有 (x='c') 的行的值。在这种情况下,16。
  9. xInverse(5) - 从窗口中删除值“5”。
  10. xStep(1) - 将值“1”添加到窗口。
  11. xValue() - 调用以获取行 (x='d') 的值。
  12. xInverse(3) - 从窗口中删除值“3”。该窗口现在仅包含值 8 和 1。
  13. xValue() - 调用以获取行 (x='e') 的值。9.
  14. xFinal() - 调用以回收任何分配的资源。

6.历史

窗口函数支持首先添加到 SQLite 版本 3.25.0 (2018-09-15)。SQLite 开发人员使用PostgreSQL窗口函数文档作为窗口函数应该如何运行的主要参考。许多测试用例已针对 PostgreSQL 运行,以确保窗口函数在 SQLite 和 PostgreSQL 中以相同的方式运行。

在 SQLite版本 3.28.0 (2019-04-16) 中,windows 函数支持得到扩展,包括 EXCLUDE 子句、GROUPS 帧类型、窗口链接,以及对“<expr> PRECEDING”和“<expr> FOLLOWING”边界的支持范围帧。