小巧、快速、可靠。
三者择其二。
窗口函数

1. 窗口函数简介

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

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

窗口函数的语法如下

window-function-invocation

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

expr

filter-clause

FILTER ( WHERE expr )

window-defn

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

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

ordering-term

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

窗口函数有两种类型:聚合窗口函数内置窗口函数。每个聚合窗口函数也可以用作普通聚合函数,只需省略 OVER 和 FILTER 子句即可。此外,所有 SQLite 的内置 聚合函数 都可以通过添加适当的 OVER 子句来用作聚合窗口函数。应用程序可以使用 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 子句将命名 window-defn 子句添加到 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, '.') 结果为 'B.C.D'。

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

2.1. PARTITION 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 由四个部分组成

以下是语法详细信息

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

expr

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

如果框架类型是 RANGE 或 GROUPS,那么所有 ORDER BY 表达式具有相同值的行被视为“同行”。或者,如果不存在 ORDER BY 项,则所有行都是同行。同行始终位于同一框架中。

默认 frame-spec

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 和 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 → 对于此形式,window-defn 的 ORDER BY 子句必须只有一个项。将该 ORDER BY 项称为“X”。令 Xi 为分区中第 i 行的 X 表达式的值,令 Xc 为当前行的 X 值。非正式地,RANGE 绑定是 Xi 在 Xc 的 <expr> 内的第一行。更准确地说

      1. 如果 Xi 或 Xc 为非数字,则边界是表达式“Xi IS Xc”为 true 的第一行。
      2. 否则,如果 ORDER BY 为 ASC,则边界是 Xi>=Xc-<expr> 的第一行。
      3. 否则,如果 ORDER BY 为 DESC,则边界是 Xi<=Xc+<expr> 的第一行。
      对于此形式,<expr> 不必是整数。只要它是常量且非负,它就可以计算为实数。
    边界描述“0 PRECEDING”始终与“CURRENT ROW”含义相同。
  3. CURRENT ROW
    当前行。对于 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 子句可以采用以下四种形式之一

以下示例演示了 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-clause

FILTER ( WHERE expr )

expr

如果提供了 FILTER 子句,则仅包含expr 为真的行。聚合窗口仍然为每行返回一个值,但对于 FILTER 表达式计算结果为非真的行,不包含在任何行的窗口框架中。例如

-- 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 表达式具有相同值的行的组)。在这些情况下,frame-spec 是指定 ROWS、GROUPS 还是 RANGE 并不重要。为了进行内置窗口函数处理,对于所有 ORDER BY 表达式具有相同值的行,无论框架类型如何,都被视为同行。

大多数内置窗口函数都忽略 frame-spec,例外是 first_value()、last_value() 和 nth_value()。在内置窗口函数调用中指定 FILTER 子句是一个语法错误。

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

row_number()

当前分区内行的编号。行从 1 开始编号,按照窗口定义中 ORDER BY 子句定义的顺序编号,否则按照任意顺序编号。

rank()

每个组中第一个同行的 row_number() - 当前行的排名,带间隙。如果没有 ORDER BY 子句,则所有行都被视为同行,此函数始终返回 1。

dense_rank()

当前行同行组在其分区内的编号 - 当前行的排名,不带间隙。行从 1 开始编号,按照窗口定义中 ORDER BY 子句定义的顺序编号。如果没有 ORDER BY 子句,则所有行都被视为同行,此函数始终返回 1。

percent_rank()

尽管有这个名字,此函数始终返回 0.0 到 1.0 之间的值,等于 (rank - 1)/(partition-rows - 1),其中 rank 是内置窗口函数 rank() 返回的值,而 partition-rows 是分区中的总行数。如果分区仅包含一行,则此函数返回 0.0。

cume_dist()

累积分布。计算为 row-number/partition-rows,其中 row-number 是组中最后一个同行返回的 row_number() 的值,而 partition-rows 是分区中的行数。

ntile(N)

参数 N 被视为整数。此函数将分区尽可能均匀地划分为 N 个组,并为每个组分配 1 到 N 之间的整数,按照 ORDER BY 子句定义的顺序,或者按照任意顺序。如果需要,较大的组先出现。此函数返回分配给当前行所属组的整数值。

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

lag() 函数的第一种形式返回对分区中前一行评估表达式 expr 的结果。或者,如果没有前一行(因为当前行是第一行),则返回 NULL。

如果提供了 offset 参数,则它必须是非负整数。在这种情况下,返回的值是对分区中当前行之前 offset 行的行的 expr 的评估结果。如果 offset 为 0,则对当前行评估 expr。如果没有当前行之前 offset 行,则返回 NULL。

如果还提供了 default,则如果由 offset 标识的行不存在,则返回它而不是 NULL。

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

lead() 函数的第一种形式返回对分区中下一行评估表达式 expr 的结果。或者,如果没有下一行(因为当前行是最后一行),则返回 NULL。

如果提供了 offset 参数,则它必须是非负整数。在这种情况下,返回的值是对分区中当前行之后 offset 行的行的 expr 的评估结果。如果 offset 为 0,则对当前行评估 expr。如果没有当前行之后 offset 行,则返回 NULL。

如果还提供了 default,则如果由 offset 标识的行不存在,则返回它而不是 NULL。

first_value(expr)

此内置窗口函数以与聚合窗口函数相同的方式为每行计算窗口框架。它返回对每行窗口框架中第一行评估的 expr 的值。

last_value(expr)

此内置窗口函数以与聚合窗口函数相同的方式为每行计算窗口框架。它返回对每行窗口框架中最后一行评估的 expr 的值。

nth_value(expr, N)

此内置窗口函数以与聚合窗口函数相同的方式为每行计算窗口框架。它返回对窗口框架中第 N 行评估的 expr 的值。行从 1 开始编号,按照存在 ORDER BY 子句时的顺序编号,或者按照其他情况下的任意顺序编号。如果分区中不存在第 N 行,则返回 NULL。

本节中的示例使用 之前定义的 T1 表 以及以下 T2 表

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()。lag() 和 lead() 忽略 frame-spec,但 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”。为了使用窗口链接,以下所有条件都必须为真

以下两段 SQL 代码类似,但并不完全等效,因为如果窗口“win”的定义包含框架规范,则后者将失败。

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

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

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

回调描述
xStep此方法是窗口聚合和传统聚合函数实现都需要的。它被调用以将一行添加到当前窗口。传递给 xStep 实现的函数参数(如果有)对应于正在添加的行。
xFinal此方法是窗口聚合和传统聚合函数实现都需要的。它被调用以返回聚合的当前值(由当前窗口的内容确定),并释放之前调用 xStep 时分配的任何资源。
xValue此方法仅适用于窗口聚合函数。此方法的存在是区分窗口聚合函数和传统聚合函数的关键。此方法被调用以返回聚合的当前值。与 xFinal 不同,实现不应删除任何上下文。
xInverse此方法仅适用于窗口聚合函数,不适用于传统聚合函数实现。它被调用以从当前窗口中删除最旧的当前聚合结果 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. xFinal() - 用于回收任何已分配的资源,并获取行 (x='e') 的值。9。

如果用户在 SQLite 调用 xFinal() 之前,通过在语句句柄上调用 sqlite3_reset() 或 sqlite3_finalize() 来放弃查询执行,那么 xFinal() 会从 sqlite3_reset() 或 sqlite3_finalize() 调用中自动调用,以便回收任何已分配的资源,即使不需要该值。在这种情况下,xFinal() 实现返回的任何错误都会被静默丢弃。

6. 历史

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

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

此页面上次修改于 2024-04-16 17:22:18 UTC