#SQLで高度な処理を行う
##ウインドウ関数
別名OLAP関数でデータベースを使ってリアルタイムにデータ分析を行う処理。
市場分析、財務諸表作成、計画作成などの用途がある。
ウインドウ関数として使える関数
集約関数(SUM、AVG、COUNT、MAX、MIN)
RANK、DENSE_RANK、ROW_NUMBERなどのウインドウ専用関数
##RANK
レコードのランキングを算出する関数。
たとえば商品分類別に販売単価の安い順で並べたランキング表。
PARTITION BY はテーブルを複数の部分(ウインド)に小分けしたうえで、ウインド関数を使いたい場合のオプション。
ORDER BY はどの列をどんな順序で順位をつけるか指定する。
select shohin_mei, shohin_bunrui, hanbai_tanka,
rank () over (partition by shohin_bunrui order by hanbai_tanka) as ranking
from shohin;
shohin_mei | shohin_bunrui | hanbai_tanka | ranking
----------------+---------------+--------------+---------
Tシャツ | 衣服 | 1000 | 1
カッターシャツ | 衣服 | 4000 | 2
ボールペン | 事務用品 | 100 | 1
穴あけパンチ | 事務用品 | 500 | 2
フォーク | キッチン用品 | 500 | 1
おろしがね | キッチン用品 | 880 | 2
包丁 | キッチン用品 | 3000 | 3
圧力鍋 | キッチン用品 | 6800 | 4
(8 rows)
上記はPARTITION BYを商品分類に指定することで、商品グループ内でのランキングを出力している。
PARTITION BYを指定しないと以下のようになる。
select shohin_mei, shohin_bunrui, hanbai_tanka,
rank () over ( order by hanbai_tanka) as ranking from shohin;
shohin_mei | shohin_bunrui | hanbai_tanka | ranking
----------------+---------------+--------------+---------
ボールペン | 事務用品 | 100 | 1
フォーク | キッチン用品 | 500 | 2
穴あけパンチ | 事務用品 | 500 | 2
おろしがね | キッチン用品 | 880 | 4
Tシャツ | 衣服 | 1000 | 5
包丁 | キッチン用品 | 3000 | 6
カッターシャツ | 衣服 | 4000 | 7
圧力鍋 | キッチン用品 | 6800 | 8
(8 rows)
###RANK関数
ランキングを算出。
同順位が複数レコード存在した場合
後続の順位が飛ぶ。
1位が3レコードある場合: 1位、1位、1位、4位...
###DENSE_RANK関数
ランキングを算出。
同順位が複数レコード存在した場合
降順の順位が飛ばない。
1位が3レコードある場合: 1位、1位、1位、2位...
###ROW_NUNBER関数
一意な連番を付与する。
1位が3レコードある場合: 1位、2位、3位、4位...
select shohin_mei, shohin_bunrui, hanbai_tanka,
rank () over (order by hanbai_tanka) as ranking,
dense_rank () over (order by hanbai_tanka) as dense_ranking,
row_number () over (order by hanbai_tanka) as row_num
from shohin;
shohin_mei | shohin_bunrui | hanbai_tanka | ranking | dense_ranking | row_num
----------------+---------------+--------------+---------+---------------+---------
ボールペン | 事務用品 | 100 | 1 | 1 | 1
フォーク | キッチン用品 | 500 | 2 | 2 | 2
穴あけパンチ | 事務用品 | 500 | 2 | 2 | 3
おろしがね | キッチン用品 | 880 | 4 | 3 | 4
Tシャツ | 衣服 | 1000 | 5 | 4 | 5
包丁 | キッチン用品 | 3000 | 6 | 5 | 6
カッターシャツ | 衣服 | 4000 | 7 | 6 | 7
圧力鍋 | キッチン用品 | 6800 | 8 | 7 | 8
(8 rows)
##集約関数とウインド専用関数
###SUM
SUMを使って累計総額を算出する
select shohin_id, shohin_mei, hanbai_tanka,
sum(hanbai_tanka) over (order by shohin_id) as current_sum
from shohin;
shohin_id | shohin_mei | hanbai_tanka | current_sum
-----------+----------------+--------------+-------------
0001 | Tシャツ | 1000 | 1000 -- 1000
0002 | 穴あけパンチ | 500 | 1500 -- 1000 + 1500
0003 | カッターシャツ | 4000 | 5500 -- 1000 + 1500 + 5500 etc
0004 | 包丁 | 3000 | 8500
0005 | 圧力鍋 | 6800 | 15300
0006 | フォーク | 500 | 15800
0007 | おろしがね | 880 | 16680
0008 | ボールペン | 100 | 16780
(8 rows)
###AVG
平均はこちら
select shohin_id, shohin_mei, hanbai_tanka,
avg(hanbai_tanka) over (order by shohin_id) as current_avg
from shohin;
shohin_id | shohin_mei | hanbai_tanka | current_avg
-----------+----------------+--------------+-----------------------
0001 | Tシャツ | 1000 | 1000.0000000000000000 -- (1000)/1
0002 | 穴あけパンチ | 500 | 750.0000000000000000 -- (1000 + 500)/2
0003 | カッターシャツ | 4000 | 1833.3333333333333333 -- (1000 + 500 + 4000)/3 etc
0004 | 包丁 | 3000 | 2125.0000000000000000
0005 | 圧力鍋 | 6800 | 3060.0000000000000000
0006 | フォーク | 500 | 2633.3333333333333333
0007 | おろしがね | 880 | 2382.8571428571428571
0008 | ボールペン | 100 | 2097.5000000000000000
(8 rows)
###移動平均
こちらは直近3行の移動平均
2行前までというフレーム指定をしている。
select shohin_id, shohin_mei, hanbai_tanka,
avg(hanbai_tanka) over (order by shohin_id rows 2 preceding) as moving_avg
from shohin;
shohin_id | shohin_mei | hanbai_tanka | moving_avg
-----------+----------------+--------------+-----------------------
0001 | Tシャツ | 1000 | 1000.0000000000000000 -- (1000)/1
0002 | 穴あけパンチ | 500 | 750.0000000000000000 -- (1000 + 500)/2
0003 | カッターシャツ | 4000 | 1833.3333333333333333 -- (1000 + 500 + 4000)/3
0004 | 包丁 | 3000 | 2500.0000000000000000 -- (500 + 4000 + 3000)/3
0005 | 圧力鍋 | 6800 | 4600.0000000000000000 -- (4000 + 3000 + 6800)/3 etc
0006 | フォーク | 500 | 3433.3333333333333333
0007 | おろしがね | 880 | 2726.6666666666666667
0008 | ボールペン | 100 | 493.3333333333333333
(8 rows)
ROWSは「行」でPRECEDINGは「前」のというキーワードを使い「~行前まで」というフレーム指定をしている。
「ROWS 2 PRECEDING」は「2行前まで」というフレーム指定になり、対象レコードは以下。
・カレントレコード
・1行前のレコード
・2行前のレコード
FOLLOWINGというキーワードは「〜行後まで」というフレーム指定も可能。
select shohin_id, shohin_mei, hanbai_tanka,
avg(hanbai_tanka) over (order by shohin_id rows between 1 preceding and 1 following) as moving_avg
from shohin;
shohin_id | shohin_mei | hanbai_tanka | moving_avg
-----------+----------------+--------------+-----------------------
0001 | Tシャツ | 1000 | 750.0000000000000000 -- (1000 + 500)/2
0002 | 穴あけパンチ | 500 | 1833.3333333333333333 -- (1000 + 500 + 4000)/3
0003 | カッターシャツ | 4000 | 2500.0000000000000000 -- (500 + 4000 + 3000)/3
0004 | 包丁 | 3000 | 4600.0000000000000000 -- (4000 + 3000 + 6800)/3 etc
0005 | 圧力鍋 | 6800 | 3433.3333333333333333
0006 | フォーク | 500 | 2726.6666666666666667
0007 | おろしがね | 880 | 493.3333333333333333
0008 | ボールペン | 100 | 490.0000000000000000
(8 rows)
1行前のレコード
カレントレコード
1行後のレコード
という3行の指定。
RANK () OVER (ORDER BY hanbai_tanka)
のORDER BY句 は決してRANKの順序を指定している訳ではない。
なのでRANKING順にしたい場合は以下のように記述する。
select shohin_mei, shohin_bunrui, hanbai_tanka,
rank () over (order by hanbai_tanka) as ranking
from shohin
order by ranking;
###GROUPING演算子
GROUP BY句だけだと合計と小計を同時に求めることはできない。
select shohin_bunrui, sum(hanbai_tanka)
from shohin
group by shohin_bunrui;
shohin_bunrui | sum
---------------+-------
キッチン用品 | 11180
衣服 | 5000
事務用品 | 600
(3 rows)
GROUP BY句だけだと上記のようになる。
なので合計行も一緒に求めたい場合、
合計行と商品分類ごとの集約結果を別個にもとめ、UNION ALLをつかって「ドッキング」させる。
select '合計' as shohin_bunrui, sum(hanbai_tanka)
from shohin
union all
select shohin_bunrui, sum(hanbai_tanka)
from shohin
group by shohin_bunrui;
shohin_bunrui | sum
---------------+-------
合計 | 16780
キッチン用品 | 11180
衣服 | 5000
事務用品 | 600
(4 rows)
上記のようにできるが、select文を2回実行して、その結果をくっつけるので、
冗長で、DBMS内での処理コストも高くつく。
これを同時にやるのがGROPPING演算子。
GROUPING演算子には
ROLLUP
CUBE
GROUPING SETS
という3種類がある。
###ROLLUP
合計値を一緒に求めることができるのがROLLUP
select shohin_bunrui, sum(hanbai_tanka) as sum_tanka
from shohin
group by rollup(shohin_bunrui);
shohin_bunrui | sum_tanka
---------------+-----------
| 16780
キッチン用品 | 11180
衣服 | 5000
事務用品 | 600
(4 rows)
ここで登録日をGROUP BY句で追加してみる。
ますはROLLUPなし
select shohin_bunrui, torokubi, sum(hanbai_tanka) as sum_tanka
from shohin
group by shohin_bunrui, torokubi;
shohin_bunrui | torokubi | sum_tanka
---------------+------------+-----------
衣服 | | 4000
キッチン用品 | 2009-01-15 | 6800
衣服 | 2009-09-20 | 1000
キッチン用品 | 2008-04-28 | 880
事務用品 | 2009-11-11 | 100
事務用品 | 2009-09-11 | 500
キッチン用品 | 2009-09-20 | 3500
(7 rows)
ROLLUPありで実行する
```sql
select shohin_bunrui, torokubi, sum(hanbai_tanka) as sum_tanka
from shohin
group by rollup(shohin_bunrui, torokubi)
order by shohin_bunrui;
shohin_bunrui | torokubi | sum_tanka
---------------+------------+-----------
衣服 | 2009-09-20 | 1000
衣服 | | 4000
衣服 | | 5000 -- 小計
事務用品 | 2009-09-11 | 500
事務用品 | 2009-11-11 | 100
事務用品 | | 600 -- 小計
キッチン用品 | 2009-01-15 | 6800
キッチン用品 | 2009-04-28 | 880
キッチン用品 | 2009-09-20 | 3500
キッチン用品 | | 11180 -- 小計
| | 16780 -- 総計
(11 rows)
ROLLUPなしの場合は衣服の合計しか出力されていない。
ROLLUPありの場合は全ての分類の合計と、分類ごとの合計を表示できる。
ただし、登録日に入ってるNULLと合計が出力されているNULLどっちがどっちかわかりにくい。
なのでGROUPING関数を使う。
GROUPING関数は超集合行で生じたNULLなら1、それ以外は0を返す。
```sql
select grouping (shohin_bunrui) as shohin_bunrui,
grouping(torokubi) as torokubi, sum(hanbai_tanka) as sum_tanka
from shohin
group by rollup(shohin_bunrui, torokubi);
shohin_bunrui | torokubi | sum_tanka
---------------+----------+-----------
1 | 1 | 16780
0 | 0 | 4000
0 | 0 | 6800
0 | 0 | 1000
0 | 0 | 880
0 | 0 | 100
0 | 0 | 500
0 | 0 | 3500
0 | 1 | 11180
0 | 1 | 5000
0 | 1 | 600
(11 rows)
上記のように1の部分は合計が返っているので、その1に文字列を与えると以下のようになる。
select case when grouping(shohin_bunrui) = 1
then '商品分類 合計'
else shohin_bunrui end as shohin_bunrui,
case when grouping(torokubi) = 1
then '登録日 合計'
else cast(torokubi as varchar(16)) end as torokubi,
sum(hanbai_tanka) as sum_tanka
from shohin
group by rollup(shohin_bunrui, torokubi);
shohin_bunrui | torokubi | sum_tanka
---------------+-------------+-----------
商品分類 合計 | 登録日 合計 | 16780
衣服 | | 4000
キッチン用品 | 2009-01-15 | 6800
衣服 | 2009-09-20 | 1000
キッチン用品 | 2008-04-28 | 880
事務用品 | 2009-11-11 | 100
事務用品 | 2009-09-11 | 500
キッチン用品 | 2009-09-20 | 3500
キッチン用品 | 登録日 合計 | 11180
衣服 | 登録日 合計 | 5000
事務用品 | 登録日 合計 | 600
(11 rows)
###CUBE
データで積み木を作る
GROUP BY句に与えられた集約キーの「すべての可能な組み合わせ」を一つの結果に含める機能。
GROUPING SETS
これはROLLUPやCUBEで求めた結果の一部のレコードだけを求めれば良い場合に使う。
select case when grouping(shohin_bunrui) = 1
then '商品分類 合計'
else shohin_bunrui end as shohin_bunrui,
case when grouping(torokubi) = 1
then '登録日 合計'
else cast(torokubi as varchar(16)) end as torokubi,
sum(hanbai_tanka) as sum_tanka
from shohin
group by grouping sets (shohin_bunrui, torokubi);
shohin_bunrui | torokubi | sum_tanka
---------------+-------------+-----------
キッチン用品 | 登録日 合計 | 11180
衣服 | 登録日 合計 | 5000
事務用品 | 登録日 合計 | 600
商品分類 合計 | | 4000
商品分類 合計 | 2009-11-11 | 100
商品分類 合計 | 2009-09-20 | 4500
商品分類 合計 | 2009-09-11 | 500
商品分類 合計 | 2009-01-15 | 6800
商品分類 合計 | 2008-04-28 | 880
(9 rows)