コマンド道場

集約関数の OVER(合計や平均を行ごとに配る)の使い方

行はそのまま、まとまりの合計が配られる

shain社員

busho_id
1
2
2
2
2
3

部署の合計を足す合計を配った結果

busho_idbusho_kei
1980000
22215050
22215050
22215050
22215050
32131000

sum(salary) OVER (PARTITION BY busho_id) AS busho_kei

  • 青い列= 式から作った列。元の表には無い列です
  • 社員 18合計を配った結果 18(行数は変わりません。減らすのは WHERE の仕事です)

同じ部署の行には同じ値が入ります。部署 2 の 4 行はどれも 2215050 です。合計を出しても行はまとまらず、1 人 1 行のままであることに注目してください。

集約関数に OVER を付けると、行を潰さずに集計結果を各行へ配れます。 GROUP BY は 18 行を 7 行にまとめてしまいますが、OVER なら 18 行のまま「自分の部署の合計」を横に置けるので、個々の値と全体をひとつの表で見比べられます。

未確認の製品があります そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database

未確認: IBM Db2

製品ごとの対応表(根拠つき)を見る

このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。

01行を潰さずに集計する

結論: sum(salary) は行をまとめますが、sum(salary) OVER (…) はまとめません。

GROUP BY で部署ごとの合計を出すと、部署の数だけの行になります。合計は分かりますが、誰がいくらもらっているかは消えます

OVER を付けると、同じ合計を元の行それぞれの横に置きます。行数は変わりません。「自分の給与」と「自分の部署の合計」を並べて見たいときは、こちらです。

⚠️ OVER を付けたものはウィンドウ関数と呼び、GROUP BY とは別の仕組みです。同じ sum でも、OVER があるかどうかで結果の行数がまったく変わります。

OVER の中身が何を決めているか
  1. SELECT busho_id, name, salary,元の列はそのまま出せる
  2. sum(salary)足すもの
  3. OVER (PARTITION BY busho_id)★どこまでを足すか(同じ部署の行)
  4. AS busho_kei作った列に名前を付ける
  5. FROM shain行は 1 つも減らない
  6. ORDER BY busho_id, shain_id;結果の並べ方

GROUP BY が「行をまとめる指示」なのに対し、PARTITION BY は「どの行までを足すか」を決めるだけです。行はまとまりません。

GROUP BY で部署ごとの合計を出す(行が潰れる)
SELECT busho_id,
       count(*) AS ninzu,
       sum(salary) AS goukei
FROM shain
GROUP BY busho_id
ORDER BY busho_id;
busho_idninzugoukei
11980000
242215050
342131000
431617000
521078000
72903000
NULL2832000

7

7 行になります。部署ごとの合計は分かりますが、社員 1 人 1 人は消えてしまいました。

OVER で同じ合計を各行に配る(行は減らない)
SELECT busho_id,
       name,
       salary,
       sum(salary) OVER (PARTITION BY busho_id) AS busho_kei
FROM shain
ORDER BY busho_id, shain_id;
busho_idnamesalarybusho_kei
1鈴木980000980000
2佐藤7200002215050
2田中5200002215050
2中村5300002215050
2小林4450502215050
3山田7000002131000
3高橋4800002131000
3伊藤5300002131000
3松本4210002131000
4清水6500001617000
4斎藤4550001617000
4山本5120001617000
5加藤6100001078000
5吉田4680001078000
7井上505000903000
7木村398000903000
NULL渡辺400000832000
NULL大野432000832000

18

18 行のままです。合計の値は GROUP BY のときとまったく同じで、置き場所だけが違います。

02PARTITION BY を省くと全体が対象になる

結論: OVER () と空にすると、まとまりは「全行」になります。

PARTITION BYどこで区切るかを決めるものです。書かなければ区切りが無い、つまり表全体がひとつのまとまりです。

両方を並べると、区切りの有無が値の違いとしてそのまま出ます。全体の合計は全行で同じ値になり、部署の合計は部署が変わると変わります。

全体の合計と部署の合計を並べる
SELECT name,
       salary,
       sum(salary) OVER ()                      AS zentai_kei,
       sum(salary) OVER (PARTITION BY busho_id) AS busho_kei
FROM shain
ORDER BY shain_id;
namesalaryzentai_keibusho_kei
鈴木9800009756050980000
佐藤72000097560502215050
田中52000097560502215050
中村53000097560502215050
小林44505097560502215050
加藤61000097560501078000
吉田46800097560501078000
山田70000097560502131000
高橋48000097560502131000
伊藤53000097560502131000
松本42100097560502131000
井上5050009756050903000
木村3980009756050903000
清水65000097560501617000
斎藤45500097560501617000
山本51200097560501617000
渡辺4000009756050832000
大野4320009756050832000

18

全体の合計 9756050 は全行で同じ値です。部署の合計は部署が変わると変わります。開発の 4 人(佐藤・田中・中村・小林)だけが同じ 2215050 になっています。

03自分がまとまりの何割かを出す

結論: 元の値をまとまりの合計で割れば、その行が占める割合になります。

これは OVER がいちばん効く使い方です。GROUP BY では合計を出した時点で個々の値が消えているので、割り算の相手が同じ行に無いからです。OVER なら両方が同じ行に並ぶので、そのまま割れます。

⚠️ 整数どうしの割り算は整数に切り捨てられる製品があります。100.0 のように小数を混ぜてから割ってください。

部署の合計に対する割合を出す
SELECT busho_id,
       name,
       salary,
       sum(salary) OVER (PARTITION BY busho_id) AS busho_kei,
       round(100.0 * salary / sum(salary) OVER (PARTITION BY busho_id), 1) AS wariai
FROM shain
WHERE busho_id IN (2, 5)
ORDER BY busho_id, shain_id;
busho_idnamesalarybusho_keiwariai
2佐藤720000221505032.5
2田中520000221505023.5
2中村530000221505023.9
2小林445050221505020.1
5加藤610000107800056.6
5吉田468000107800043.4

6

部署 5 は 2 人しかいないので、2 人の割合を足すと 100.0 になります。部署 2 は 4 人なので 1 人あたりの割合が小さくなります。

04ORDER BY を足すと「その行まで」になる

結論: OVER の中に ORDER BY を書くと、合計の範囲が「先頭からその行まで」に変わります。 これが累計です。

ORDER BY が無いときはまとまり全体を足していました。ORDER BY を足すと、その並びで自分より前にある行と自分だけが足されます。

同じ sum でも、OVER の中身ひとつで「全体の合計」にも「累計」にもなります。最後の行の累計は、必ず全体の合計と一致します。

社員番号の順に足していく
SELECT shain_id,
       name,
       salary,
       sum(salary) OVER (ORDER BY shain_id) AS ruikei
FROM shain
ORDER BY shain_id;
shain_idnamesalaryruikei
1鈴木980000980000
2佐藤7200001700000
3田中5200002220000
4中村5300002750000
5小林4450503195050
6加藤6100003805050
7吉田4680004273050
8山田7000004973050
9高橋4800005453050
10伊藤5300005983050
11松本4210006404050
12井上5050006909050
13木村3980007307050
14清水6500007957050
15斎藤4550008412050
16山本5120008924050
17渡辺4000009324050
18大野4320009756050

18

1 行目は自分の給与そのもの、2 行目は 1 行目との合計、と増えていきます。最後の大野の 9756050 は、前の節で出た全体の合計と同じ値です。

05⚠️ よくある間違い:同じ値の行はまとめて足される

結論: 累計の並び順に同じ値の行があると、その行たちは「まとめて」足されます。 1 行ずつ増えません。

給与の高い順で累計を取ると、給与が同額の中村と伊藤はどちらも同じ累計値になります。片方だけ足した途中の値は出てきません。

これは既定の集計範囲が「同じ並び順の値を持つ行までを一括で含む」ものだからです。1 行ずつ積み上げたい場合の書き方(ROWS 指定)は製品によって対応が分かれるため、このページでは扱いません。

まずは「並びに同じ値があると段が飛ぶ」ことを知っておいてください。 累計が思った増え方をしないときは、ここを疑います。決着が付く列を並びに足せば、1 行ずつ増えるようになります。

同額の行があると段が飛ぶ
SELECT name,
       salary,
       sum(salary) OVER (ORDER BY salary DESC) AS ruikei
FROM shain
WHERE taishoku_on IS NULL
ORDER BY salary DESC, shain_id;
namesalaryruikei
鈴木980000980000
佐藤7200001700000
山田7000002400000
清水6500003050000
加藤6100003660000
中村5300004720000
伊藤5300004720000
田中5200005240000
井上5050005745000
吉田4680006213000
斎藤4550006668000
小林4450507113050
大野4320007545050
松本4210007966050
渡辺4000008366050
木村3980008764050

16

中村と伊藤はどちらも 4720000 です。1 つ前の加藤の 3660000 から、530000 が 2 人ぶん一気に足されています。

決着が付く列を足すと 1 行ずつ増える
SELECT name,
       salary,
       sum(salary) OVER (ORDER BY salary DESC, shain_id) AS ruikei
FROM shain
WHERE taishoku_on IS NULL
ORDER BY salary DESC, shain_id;
namesalaryruikei
鈴木980000980000
佐藤7200001700000
山田7000002400000
清水6500003050000
加藤6100003660000
中村5300004190000
伊藤5300004720000
田中5200005240000
井上5050005745000
吉田4680006213000
斎藤4550006668000
小林4450507113050
大野4320007545050
松本4210007966050
渡辺4000008366050
木村3980008764050

16

中村が 4190000、伊藤が 4720000 と別々になりました。社員番号を並びに足したことで、2 人の間に前後が付いたためです。

06⚠️ よくある間違い:WHERE と HAVING では使えない

結論: WHERE にも HAVING にもウィンドウ関数は書けません。エラーになります。

どちらもウィンドウ関数より先に評価されるためです。行を絞る時点では、まだ合計は配られていません。

「部署の合計が 200 万円を超える部署の社員だけ」のような条件は、いったん合計を配った結果を表として作ってから、その外側で絞り込みます

書き方は FROM の中に副問い合わせを置くか、WITH で名前を付けるかの 2 通りです。どちらでも結果は同じです。

WHERE に書くとエラーになる
SELECT name, salary
FROM shain
WHERE sum(salary) OVER (PARTITION BY busho_id) > 2000000;
ERROR: window functions are not allowed in WHERE

「window functions are not allowed in WHERE」と出ます。書き方の間違いではなく、評価される順番の問題です。

合計を配ってから、外側で絞り込む
SELECT busho_id, name, salary, busho_kei
FROM (SELECT busho_id,
             name,
             salary,
             sum(salary) OVER (PARTITION BY busho_id) AS busho_kei
      FROM shain) t
WHERE busho_kei > 2000000
ORDER BY busho_id, salary DESC;
busho_idnamesalarybusho_kei
2佐藤7200002215050
2中村5300002215050
2田中5200002215050
2小林4450502215050
3山田7000002131000
3伊藤5300002131000
3高橋4800002131000
3松本4210002131000

8

8 行返ります。部署の合計が 200 万円を超えているのは開発(2215050)と営業(2131000)の 2 部署で、その所属者だけが残っています。

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

ここで出てくる関数を引く

関連するトピック

根拠(一次情報)