SQLite のウィンドウ関数

SQLite のウィンドウ関数の使い方についてまとめます。

ウィンドウ関数の機能

SUMMAX などの集約関数の後ろに OVER 句をつけることで、列の値全体を使った計算をすることができます。集約を行うグループごとに 1 行にまとめられるのではなく、各行のデータを残したまま計算結果を付与できるのが GROUP BY 句を使う場合との大きな相違点です。

使い方

ウィンドウ関数は以下のような構文で使うことができます。

aggr(column_1) OVER(PARTITION BY column_2 ORDER BY column_3)

ここで、aggrSUMMAX などの周約関数です。

PARTITION BY

column_2 の値ごとに分類した後に集約することができます。ここは GROUP BY column_2 で集約するのと同様です。

PARTITION BY 句を省略することもでき、その場合は全体が 1 つのグループとなります。

ORDER BY

column_3 をキーとしてソートした後に、先頭行から該当する行までだけを集約することができます。この ORDER BY 句の後に ROWS BETWEEN x PRECEDING AND y FOLLOWING と続けると、該当する行の x 行前から y 行目後まで (inclusive) だけを集約することもできます。より詳細な行の指定方法については、以下の記事が参考になります。

分析関数(ウインドウ関数)をわかりやすく説明してみた #MySQL - Qiita

ORDER BY 句を省略することもでき、その場合は PARTITION BY 句で分割されたグループごとに全体を集約します。

使用例

以降では、このウィンドウ関数の具体的な使用例について見ていきます。

順位付け

以下の表 TITLE_RANKING において、TITLE の降順に順位を付与することを考えます。

NAME      STATE   TITLE
--------  ------  -----
fujii     active  19
habu      active  99
nakahara  retire  64
ooyama    retire  80
tanigawa  active  27
watanabe  active  31
yonenaga  retire  19

集約範囲を全体にして RANK 関数を適用すればよいです。

SELECT
    *,
    RANK() OVER(
        ORDER BY TITLE DESC
    ) AS RANK
FROM
    TITLE_RANKING
ORDER BY
    STATE ASC,
    RANK ASC;
NAME      STATE   TITLE  RANK
--------  ------  -----  ----
habu      active  99     1
ooyama    retire  80     2
nakahara  retire  64     3
watanabe  active  31     4
tanigawa  active  27     5
yonenaga  retire  19     6
fujii     active  19     6

STATE で分類して順位をつけることもできます。

SELECT
    *,
    RANK() OVER(
        PARTITION BY STATE
        ORDER BY TITLE DESC
    ) AS RANK
FROM
    TITLE_RANKING
ORDER BY
    STATE ASC,
    RANK ASC;
NAME      STATE   TITLE  RANK
--------  ------  -----  ----
habu      active  99     1
watanabe  active  31     2
tanigawa  active  27     3
fujii     active  19     4
ooyama    retire  80     1
nakahara  retire  64     2
yonenaga  retire  19     3

累積和

以下の表 POST_RECORD において、POST の累積和を取ることを考えます。

YEAR  MONTH  POST
----  -----  ----
2022  7      1
2023  8      3
2023  9      3
2023  10     1
2023  11     2
2023  12     2

時系列でソートし、先頭から該当行までを集約して SUM 関数を適用することで、以前の POST の総和を計算することができます。

SELECT
    *,
    SUM(POST) OVER(
        ORDER BY YEAR ASC
        MONTH ASC
    ) AS POST_SUM
FROM
    POST_RECORD
ORDER BY
    YEAR ASC,
    POST_SUM ASC;
YEAR  MONTH  POST  POST_SUM
----  -----  ----  --------
2022  7      1     1
2023  8      3     4
2023  9      3     7
2023  10     1     8
2023  11     2     10
2023  12     2     12

差分計算

以下の表 R_TRANSITION において、前回からの R の変動値を計算します。

DATE        R
----------  ----
2023-06-18  2796
2023-07-02  2802
2023-07-31  2816
2023-08-14  2853
2023-12-18  2833

ウィンドウを DATE でソートしたときの 1 行前のみに設定して MAX 関数 (MINSUM でも良い) を適用することで、直前の値を取得することができます。

SELECT
    *,
    R - MAX(R) OVER(
        ORDER BY DATE
        ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
    ) AS DELTA
FROM
    R_TRANSITION
ORDER BY
    DATE;
DATE        R     DELTA
----------  ----  -----
2023-06-18  2796
2023-07-02  2802  6
2023-07-31  2816  14
2023-08-14  2853  37
2023-12-18  2833  -20

まとめ

関数型言語っぽさがありつつも表現力が高いので面白いですね。