MySQL 8.0.2 DMRでウィンドウ関数がサポートされたので、RANK関数を試してみる の続きです。
先の記事ではRANK関数で順位付けしただけなので、SUM関数による総和、CUME_DIST関数による「上位○%」などを試してみます。
なお、テストに使うテーブルなどは先の記事のものをそのまま使います。
1. ランキング最上位からの総和を求めてみる
総和を求めるには、GROUP BYによる集約と同様**SUM()を使います。
なお、同時にRANK()による順位付けもしていますが、集計対象とする範囲・順序が同じですので、「WINDOW w AS ()」**として共用します。
mysql> SELECT RANK() OVER w AS pop_rank,
-> ctv_name, population,
-> SUM(population) OVER w AS sum_total FROM win_test.aichi
-> WINDOW w AS (ORDER BY population DESC);
+----------+-----------------+------------+-----------+
| pop_rank | ctv_name | population | sum_total |
+----------+-----------------+------------+-----------+
| 1 | 名古屋市 | 2309753 | 2309753 |
| 2 | 豊田市 | 425064 | 2734817 |
| 3 | 岡崎市 | 384224 | 3119041 |
| 4 | 一宮市 | 380574 | 3499615 |
| 5 | 豊橋市 | 373736 | 3873351 |
| 6 | 春日井市 | 307121 | 4180472 |
| 7 | 安城市 | 185920 | 4366392 |
| 8 | 豊川市 | 183134 | 4549526 |
| 9 | 西尾市 | 169130 | 4718656 |
| 10 | 刈谷市 | 151432 | 4870088 |
| 11 | 小牧市 | 149130 | 5019218 |
| 12 | 稲沢市 | 136478 | 5155696 |
| 13 | 瀬戸市 | 128396 | 5284092 |
| 14 | 半田市 | 117181 | 5401273 |
| 15 | 東海市 | 113022 | 5514295 |
(中略)
| 50 | 豊山町 | 15425 | 7503833 |
| 51 | 設楽町 | 4819 | 7508652 |
| 52 | 飛島村 | 4423 | 7513075 |
| 53 | 東栄町 | 3241 | 7516316 |
| 54 | 豊根村 | 1108 | 7517424 |
+----------+-----------------+------------+-----------+
54 rows in set (0.00 sec)
2. 実数ではなく割合を出す
次に、実数ではなく、割合を出してみます。
この場合、**「その行までの総和を全体の総和(合計)で割る」**必要がありますので、WINDOWを2つ定義し、2つ目は空(=全体)とします。
また、「20-80の法則」(上位20%で全体の80%を占める)が成立するのかを確認するため、CUME_DIST()で対象行が上位何%にあたるかを表示します。
このとき、別名「cume_dist」は関数名と同じなので、シングルクォートで囲みます。
※**PERCENT_RANK()**という関数もありますが、こちらは分子・分母とも-1して計算するものです。目的に応じて使い分けてください。
mysql> SELECT RANK() OVER w AS pop_rank,
-> ctv_name, population,
-> (SUM(population) OVER w / SUM(population) OVER w2) AS sum_pct,
-> CUME_DIST() OVER w AS 'cume_dist' FROM win_test.aichi
-> WINDOW w AS (ORDER BY population DESC), w2 AS ();
+----------+-----------------+------------+---------+----------------------+
| pop_rank | ctv_name | population | sum_pct | cume_dist |
+----------+-----------------+------------+---------+----------------------+
| 1 | 名古屋市 | 2309753 | 0.3073 | 0.018518518518518517 |
| 2 | 豊田市 | 425064 | 0.3638 | 0.037037037037037035 |
| 3 | 岡崎市 | 384224 | 0.4149 | 0.05555555555555555 |
| 4 | 一宮市 | 380574 | 0.4655 | 0.07407407407407407 |
| 5 | 豊橋市 | 373736 | 0.5152 | 0.09259259259259259 |
| 6 | 春日井市 | 307121 | 0.5561 | 0.1111111111111111 |
| 7 | 安城市 | 185920 | 0.5808 | 0.12962962962962962 |
| 8 | 豊川市 | 183134 | 0.6052 | 0.14814814814814814 |
| 9 | 西尾市 | 169130 | 0.6277 | 0.16666666666666666 |
| 10 | 刈谷市 | 151432 | 0.6478 | 0.18518518518518517 |
| 11 | 小牧市 | 149130 | 0.6677 | 0.2037037037037037 |
| 12 | 稲沢市 | 136478 | 0.6858 | 0.2222222222222222 |
| 13 | 瀬戸市 | 128396 | 0.7029 | 0.24074074074074073 |
| 14 | 半田市 | 117181 | 0.7185 | 0.25925925925925924 |
| 15 | 東海市 | 113022 | 0.7335 | 0.2777777777777778 |
| 16 | 江南市 | 98261 | 0.7466 | 0.2962962962962963 |
| 17 | 大府市 | 90995 | 0.7587 | 0.3148148148148148 |
| 18 | 日進市 | 89528 | 0.7706 | 0.3333333333333333 |
| 19 | あま市 | 87149 | 0.7822 | 0.35185185185185186 |
| 20 | 北名古屋市 | 85160 | 0.7935 | 0.37037037037037035 |
| 21 | 知多市 | 84573 | 0.8048 | 0.3888888888888889 |
(中略)
| 54 | 豊根村 | 1108 | 1.0000 | 1 |
+----------+-----------------+------------+---------+----------------------+
54 rows in set (0.00 sec)
どうやら、上位20%(小牧市あたりまで)で2/3(約67%)程度のようです。
なお、「上位20%だけ抜き出せば良いので、WHERE句で範囲を絞りたい」ところですが、MySQL 8.0では今のところ、WHERE句にはウィンドウ関数自体もその別名も指定することができないようです(「そんな列はないよ」と怒られてしまいます。このとき、別名を'cume_dist'としてしまうと文字列定数として誤認識するため、違う別名を付けて試しました)。
2019/03/29 追記:
PostgreSQLでもSELECTの射影対象とORDER BY句にしかウィンドウ関数を記述できないようです。ウィンドウ関数自体がGROUP BY句・HAVING句・WHERE句よりも後の段階で(論理的に)実行されるから、ということです。
3. 市町村の種類別に出してみる
順位付けのみの場合と同様、市町村の種類別に集計することもできます。
mysql> SELECT RANK() OVER w AS pop_rank, ctv_name, population,
-> SUM(population) OVER w AS sum_total,
-> (SUM(population) OVER w / SUM(population) OVER w2) AS sum_pct,
-> CUME_DIST() OVER w AS 'cume_dist' FROM win_test.aichi
-> WINDOW w AS (PARTITION BY ctv_type ORDER BY population DESC),
-> w2 AS (PARTITION BY ctv_type);
+----------+-----------------+------------+-----------+---------+---------------------+
| pop_rank | ctv_name | population | sum_total | sum_pct | cume_dist |
+----------+-----------------+------------+-----------+---------+---------------------+
| 1 | 名古屋市 | 2309753 | 2309753 | 1.0000 | 1 |
| 1 | 豊田市 | 425064 | 425064 | 0.3593 | 0.3333333333333333 |
| 2 | 岡崎市 | 384224 | 809288 | 0.6841 | 0.6666666666666666 |
| 3 | 豊橋市 | 373736 | 1183024 | 1.0000 | 1 |
| 1 | 一宮市 | 380574 | 380574 | 0.5534 | 0.5 |
| 2 | 春日井市 | 307121 | 687695 | 1.0000 | 1 |
| 1 | 安城市 | 185920 | 185920 | 0.0633 | 0.03125 |
| 2 | 豊川市 | 183134 | 369054 | 0.1257 | 0.0625 |
| 3 | 西尾市 | 169130 | 538184 | 0.1833 | 0.09375 |
| 4 | 刈谷市 | 151432 | 689616 | 0.2349 | 0.125 |
| 5 | 小牧市 | 149130 | 838746 | 0.2857 | 0.15625 |
(中略)
| 32 | 弥富市 | 43185 | 2935913 | 1.0000 | 1 |
| 1 | 東浦町 | 49328 | 49328 | 0.1247 | 0.07142857142857142 |
| 2 | 東郷町 | 43344 | 92672 | 0.2343 | 0.14285714285714285 |
| 3 | 武豊町 | 42604 | 135276 | 0.3420 | 0.21428571428571427 |
(中略)
| 14 | 東栄町 | 3241 | 395508 | 1.0000 | 1 |
| 1 | 飛島村 | 4423 | 4423 | 0.7997 | 0.5 |
| 2 | 豊根村 | 1108 | 5531 | 1.0000 | 1 |
+----------+-----------------+------------+-----------+---------+---------------------+
54 rows in set (0.00 sec)
なお、sum_pctの値の計算にsum_totalを再利用したいところですが、こちらも「そんな列はないよ」と怒られてしまいます。
とりあえず、今のところOracleやPostgreSQLの真似をして何かしようとするとちょっとハマる部分もありますが、基本的な部分はちゃんと動くようです。
【おまけ】
MySQL 8.0関連投稿記事へのリンクを集めました。