小巧、快速、可靠。
選擇其中三項。
視窗函數

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 y」)中的「ORDER BY」子句,將連續的整數分配給每一列。請注意,這不會影響從整體查詢返回結果的順序。最終輸出的順序仍然由附加到 SELECT 陳述式(在本例中為「ORDER BY x」)的 ORDER BY 子句控制。

也可以使用 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> 必須是一個非負的常數數值表達式。邊界是在當前列之前 <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」為真的第一列。
      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 為 true 的列才會包含在視窗框中。聚合視窗仍然會為每一列傳回一個值,但 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 表達式值相同的列)。在這些情況下,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 個群組,並依 ORDER BY 子句定義的順序,或在沒有 ORDER BY 子句的情況下以任意順序,將 1 到 N 之間的整數指派給每個群組。如有必要,較大的群組會先出現。此函數返回指派給目前列所屬群組的整數值。

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 的值。如果存在 ORDER BY 子句,則會按照其定義的順序從 1 開始對視窗框架中的列進行編號,否則將以任意順序編號。如果分區中沒有第 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()。 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"。為了使用視窗鏈結,必須滿足以下所有條件

以下兩個 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() 之前,透過在陳述式 handle 上調用 sqlite3_reset() 或 sqlite3_finalize() 來放棄查詢執行,則即使不需要該值,也會在 sqlite3_reset() 或 sqlite3_finalize() 調用內自動調用 xFinal() 以回收任何已配置的資源。在這種情況下,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