Sqlite: window function

原文地址

Sqlite Window Function

簡介

之前我們接觸的SQL命令的結果,一般都是逐行的。即SQL命令返回的結果,都是來自原表的同一行。Window Function則賦予了我們在SQL
結果中,獲得來自一組行的數據的能力。這樣的組被稱爲「Window」。

Window Function最鮮明的特徵是OVER關鍵字。如果 以一個函數有OVER子句,則此函數爲Window Function。反之,如果這個函數不帶OVER子句,則這個函數是簡單的聚合(Aggregate)函數或者標量(Scalar)函數。Window Function在函數和OVER子句之間,還可能帶有FILTER子句。

Window Function的語法結構如下:

不同於普通的函數,Window Function不能使用Distinct子句。另外,Window Function只能出現在查詢結果中和ORDER BY後面。

Window Function可以劃歸爲的兩種不同類型:聚合窗函數(Aggregate Window Function)和內建窗函數(Built-in Window Function)。每個聚合窗函數也可以當做普通的聚合函數使用(只需要捨去OVERFILTER子句即可)。內建窗函數,也可以通過合適地配置OVER子句從而具備聚合函數的功能。在應用中,我們也可以通過sqlite3_create_window_function()接口(C)來自定義新的聚合窗函數。

下面是使用內建的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()窗函數函數可以每行添加一個行號。行號的順序通過OVER後面的ORDER BY y確定。注意,OVER後面的ORDER BY y不會影響SELECT返回的查詢結果的順序。在上面的例子中,SELECT返回的順序還是根據x來排序的。比對上面的「Window function invocation」圖,OVER後的子句體稱爲window-defn。我們還可以在SELECT語句中通過WINDOW子句來聲明named window-defn

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之前。

聚合窗函數

在這個部分我們假設所有的數據庫的結構都是:

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'  );

聚合窗函數類似於一般的聚合函數,添加聚合窗函數不會改變查詢返回的行數。相反,聚合窗函數會將於「Window frame」中運行的得到的聚合結果添加到原本的每一行結果中。例如

-- 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;

在上面的例子中,我們要做的將本行與上下兩行的結果拼起來,而上下行關係,是根據OVER子句中的ORDER BY來確定的。

PARTITION BY 子句

爲了計算窗函數,查詢的返回結果通過PARTITION BY子句分割成多個「partitions」。PARTITION BY類似於GROUP BY,可以將查詢結果中,於PARTITION BY後的window-defn所指定列擁有相通值的行組成組。若沒有PARTITION BY子句,則所有的查詢結果組成一個單一的組。窗函數在各個「partition」上運行。

例如

-- 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將查詢結果劃分成了三個Partition。第一個Parition的c = one,第二個Partition的c = three,第三個Partition的c = two。注意,Partiion的劃分,及其後續的的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;

Frame Specification

Frame Specification是OVER子句的一個部分,規定了聚合窗函數讀取的輸出行的範圍。frame-specwindow-defn中的位置如下:

frame-spec包含如下四個部分:

  • Frame type: either ROWS, RANGE or GROUPS;
  • A starting frame boundary;
  • An ending frame broundary;
  • An EXCLUDE clause;

細節的語法結構如下:

其中ending frame boundary可以被省略,此時默認情況下ending frame boundary默認爲 CURRENT ROW

如果frame type爲RANGE或者GROUPS,那麼在ORDER BY所指定的列上具有相同值的行被歸爲一組「peers」。如果沒有ORDER BY,那麼所有的行歸於一組Peer。注意Peers總是屬於相同的frame。

默認的frame-spec

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS

默認的配置的意思是,聚合窗函數從Partition的開頭開始讀取直到當前的行的所有Peers。同Peer組的行對從窗函數獲取的返回值是相通的(其Window frame是相同的)。例如

-- 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;

關於Frame的更多細節,參考出處原文(頁面頂部)

FILTER 子句

如果出現了FILTER子句,那麼只有expr指定的行纔會被包含到window frame中。這裏的FILTER不會過濾查詢結果,只是決定了窗函數作用的範圍。

內建窗函數

內建窗函數也具備和聚合窗函數同樣的PARTITION BY子句功能:每個行都從屬於一個Partition,而每個Partition被單獨地進行處理。ORDER BY的作用,我們在下面進行闡述。有一些特定的窗函數(rank(), dense_rank, percent_rank and ntile())採用了peer group的概念(rows within the same partition that have the same values for all ORDER BY expressions)。此時frame-specframe type(ROWS, GROUPS, RANGE) 就不起作用了。

SQLite支持如下11個內建的窗函數

  • row_number(): 當前行位於Partition中的位置(行號),從1開始排列,順序由窗函數的ORDER BY決定。
  • rank(): 每一個Group(同一個Partition內在ORDER BY指定的列上具有相同值的行歸於一個Group)中的第一個peer(行)的row_number值。rank獲取的序號可能是不連續的。
  • dense_rank(): 相比於rank(), 壓縮了序號的間隙,得到的序號總是連續的。從1開始排。
  • percent_rank(): 將rank轉化成百分比,等於(rank - 1)/(partition-rows - 1)。如果只有一個組,返回0.
  • cume_dist(): 累積分佈,等於row-number/partition-rows
  • ntile(N): 參數N爲整數,這個函數將partition劃分爲儘可能均勻的N份,併爲每份分配一個1到N的整數,順序由ORDER BY決定(若無ORDER BY,則爲亂序)。如果需要的話,較大的組會先出現。
  • lag(expr)
  • lag(expr, offset)
  • lag(expr, offset, default): 返回對上一行執行expr得到的結果。如果沒有上一行,返回空。可以通過offset修改偏移量(如設爲2,返回往上數第二行執行結果,必須爲費複製)。offset爲0表示對當前行執行。default表示目標行不存在時需要返回的默認值。
  • lead(expr)
  • lead(expr, offset)
  • lead(expr, offset, default): 和lag函數類似,不過是向下獲取。
  • first_value(expr): 返回第一個行的數據
  • last_value(expr): 返回最後一行的數據
  • nth_value(expr, N): 返回第N行的數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章