SQL

WITHIN GROUP句の使い方

PostgreSQLのWITHIN GROUP句の使い方について説明します。

個人的に、ふとWITHIN GROUP句とOVER句の違いがわからぬわあ!と思ったので、忘備録的に残すことにした次第です。

なんとなくOVER句に似てると思いませんかね。理解してみるとまあまあ違いましたけど
そういうわけでOVER句と比べるような書き方をここではやらせていただきます。

以下で具体的な使用例を見せて違いを説明しよう思いますが、まずその前に結論、

  • WITHIN GROUP
    → 集約関数の中でも特に順序集合集約関数仮想集合集約関数と使われ、
    昇順、または降順に順番を並び替えるための句であるORDER BY句が()に入る
  • OVER
    → 分析関数ウィンドウ関数)の適用範囲範囲を指定

という違いがあります。

ではまず、集約関数分析関数とはなんでしょうか?

以下でまずそれらの違いについて説明しまして、

その後でWITHIN GROUPとOVERの比較する話します。

集計関数と分析関数の違い

ここでは、"集計関数COUNT()"と"分析関数COUNT()"について説明します。

(ちなみに集計関数COUNT()は、特に汎用集約関数といって、WITHIN GROUP句は使えない、使う必要が無いようです)

下の特に意味のない例のテーブル(?)、testテーブルを用います。

test テーブル

item
AAA
BBB
AAA
AAA
BBB
DDD

このテーブルに対して以下のようなSQLを見て頂ければ、集計関数と分析関数の違いが分かるかと思います。

-- 集計関数
SELECT COUNT(*) AS cnt 
FROM test;
-- ↓ 結果
cnt
--------------
            6

-- 分析関数
SELECT item, COUNT(*) OVER () AS cnt 
FROM test;
-- ↓ 結果 
item       cnt
---------- -------------- 
AAA                   6
BBB                   6 
AAA                   6
AAA                   6 
BBB                   6 
DDD                   6 

集計関数COUNT()の結果(AS句を使って"cnt"とした)についてはご存知の方が多いでしょう。任意の列(*で表されている)のフィールド(項目)の数=6だということです。つまり行の数ですね

分析関数COUNT()のほうは、6行のテーブルが出てきて、1つ1つの行に全行数6が現れました。

このように分析関数は、行をまとめず、1つ1つの行に複数行による計算値結果を与えます

またOVER句の()にはどの行を計算に使うか、という範囲指定が書けますが、上の例ではそれが無いので、全ての行を数えて6を与えています。

何も()に範囲指定が書かれていないOVER句は、集計関数ではなく分析関数ですよ、と言っているだけ

と解釈できます。OVER()が無かったらCOUNT()は集計関数ですから。そして項目の数が他の2つの列と合わなくて(1と6で)、エラーになりますよ。

次に集計関数の中でも特に、WITHIN GROUP句を使う仮想集合集約関数について説明させていただきます。

仮想集合集約関数、WITHIN GROUP句
と分析関数、OVER句の比較

ここでは”仮想集合集約関数RANK()”と"分析関数RANK()"をそれぞれを比較します。

(ちなみに仮想集合集約関数はランキングに関する3つの関数しか無いようです、参考: https://www.postgresql.jp/docs/9.5/functions-aggregate.html

以下に2つのRANK()を使ったSQLの例を見せます。

-- 仮想集合集約関数
SELECT 
RANK('CCC') WITHIN GROUP(ORDER BY item) as rank_asc
, RANK('CCC') WITHIN GROUP(ORDER BY item DESC) as rank_desc
FROM test; 
-- ↓ 結果
rank_asc       rank_desc
-------------- --------------
            6               2

-- 分析関数
SELECT item
, RANK() OVER(ORDER BY item) as rank_asc
, RANK() OVER(ORDER BY item DESC) as rank_desc
FROM test;
-- ↓ 結果 
item       rank_asc       rank_desc
---------- -------------- -------------- 
AAA                   1               4
AAA                   1               4
AAA                   1               4
BBB                   4               2
BBB                   4               2
DDD                   6               1

仮想集合集約関数RANK() は、ORDER BY句で指定したカラムに"仮想的に"RANK()の引数の値(上の例では 'CCC')を入れたとき、それがランキング何位であるかを返します。

ここでは文字列のランキングなので、アルファベット順にランキングが決められています。
また、デフォルトでは昇順(ASC)ですが、"ODER BY カラム名"の後に"DESC"と書くことで、降順にできます。

もちろん複数行の情報が一行にまとまるということになります。集計関数ですから

一方。分析関数RANK()はORDER BY句で指定したカラムの値によるそれぞれの行列のランキングを返します。
もちろん行はまとまりません。分析関数ですから

(ついでに元の順番から"item"の昇順に並び替えられて表示されるようです。)

ついでに、
よく使う順序集合集約関数PERCENTILE_CONT()

順序集合集約関数は3つしか無いようです
(参考: https://www.postgresql.jp/docs/9.5/functions-aggregate.html)が、

その1つとしてよく使う PERCENTILE_CONT() について説明しときます。

これはパーセンタイル値と呼ばれる、中央値の概念を広くしたものを返します。

引数には割合として0から1までの値が入り、
割合0.5(50%)のパーセンタイル値 PERCENTILE_CONT(0.5) = 中央値 です。

下の特に意味のない例のテーブル(?)、test2テーブルを用います。

test2 テーブル

number
1
2
2
5
7
8

SELECT 
PECENTILE_CONT(0.2) WITHIN GROUP(ORDER BY number) as p20
, PECENTILE_CONT(0.2) WITHIN GROUP(ORDER BY number) as p80
FROM test2; 
-- ↓ 結果
p20        p80
---------- -------------- 
      2.0            7.0

ちなみに例ではPERCENTILE_CONT() の引数を 0.2と0.8 としたので小さいほうから数えて全体の20%, 80%のnumberカラムの値、2と7が返されています。

何か間違いがありましたらコメントを頂ければ幸いです。

-SQL
-, ,