コマンド道場

集約関数(COUNT / SUM / AVG / MIN / MAX)の使い方

たくさんの行が、1 つの値になる

shain社員

busho_idsalary
1980000
2720000
2530000
2520000

集約まとめた結果

busho_idcount(salary)
11
24
34

GROUP BY busho_id

  • 社員 16 が、6にまとまります(同じ busho_id の行が 1 行になる)。
  • count(salary)まとまりごとに計算した値で、元の表には無い列です。

ここでは部署ごとにまとめていますが、まとめる単位を指定しなければ「表全体で 1 つ」になります。

集約関数は、たくさんの行を 1 つの値にまとめる道具です。いちばんの落とし穴は NULL の扱いで、count(*) は行を数え、count(列) は値のある行だけを数えます。sumavg も NULL を無視するため、平均の分母が思っているものと違うことがあります。

主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2

製品ごとの対応表(実測)を見る

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

01表全体を 1 行にまとめる

結論: 集約関数を書くと、結果は 1 行になります。

GROUP BY を書かなければ、表のすべての行が 1 つのまとまりとして扱われます。18 行あった社員表が、1 行の集計結果になります。

何行あっても結果が 1 行になるので、「絞り込みが効いているか」は件数を一緒に出して確かめるのが確実です。

1 行ずつ、何をしているか
  1. SELECT count(*) AS ninzu,行の数
  2. max(salary) AS saikou,最大値
  3. min(salary) AS saitei最小値
  4. FROM shainどの表を見るか

集約関数はいくつでも並べられます。すべて同じ行の集まりに対して計算されます。

社員表を 1 行にまとめる
SELECT count(*) AS ninzu, max(salary) AS saikou, min(salary) AS saitei FROM shain;
ninzusaikousaitei
18980000398000

1

18 行が 1 行になりました。何行あっても結果は 1 行です。

02⚠️ count(*) と count(列) は違う

結論: **count(*) は行を数え、count(列) はその列が NULL でない行だけを数えます。**

この表には、部署に所属していない社員(busho_id が NULL)がいます。そのため count(*)count(busho_id) の値がずれます。

差がそのまま「所属が決まっていない人数」になるので、2 つを並べて出すと欠損の件数が分かります

行数と、値がある行数を並べる
SELECT count(*) AS zen, count(busho_id) AS shozoku_ari, count(*) - count(busho_id) AS shozoku_nashi FROM shain;
zenshozoku_arishozoku_nashi
18162

1

差の 2 人が「部署が決まっていない社員」です。count(列) は NULL を数えません。

本当にその 2 人かを確かめる
SELECT name, busho_id FROM shain WHERE busho_id IS NULL ORDER BY shain_id;
namebusho_id
渡辺NULL
大野NULL

2

集計の差が何を意味するかは、こうして元の行を見れば確かめられます。

03⚠️ 平均の分母は「値がある行数」

結論: `avg` は NULL を無視します。分母は「その列に値が入っている行数」です。

sum も同じく NULL を飛ばします。したがって avg(列)sum(列) / count(列) と一致し、sum(列) / count(*) とは一致しません

「NULL を 0 とみなして平均したい」なら、先に COALESCE(列, 0) で埋めてから計算します。どちらが正しいかは業務の定義次第なので、必ず意識して選んでください

合計・行数・値のある行数・平均を並べる
SELECT sum(salary) AS goukei, count(*) AS gyosu, count(salary) AS atai_ari, avg(salary) AS heikin FROM shain;
goukeigyosuatai_ariheikin
97560501818542002.777777777778

1

salary には NULL が無いので、ここでは行数と値のある行数が同じです。だから avg は「合計 ÷ 行数」と一致します。

NULL がある列で数えるとどうなるか
SELECT count(*) AS gyosu, count(taishoku_on) AS taishoku_ari, avg(salary) AS zenin_heikin FROM shain;
gyosutaishoku_arizenin_heikin
182542002.777777777778

1

taishoku_on は退職者にしか入っていないため、count(taishoku_on) は 2 になります。同じ表でも、どの列を数えるかで結果が変わります。

04絞り込んでから集計する

結論: `WHERE` は集計より先に効きます。

集約関数が見るのは「WHERE で絞ったあとの行」です。したがって「在籍者だけの平均」は WHERE で退職者を落としてから avg を取ります。

順番は FROMWHERE → 集約です。集約した結果に条件を付けたいときは `HAVING` を使います(WHERE には書けません)。

在籍者だけで数える
SELECT count(*) AS ninzu, round(avg(salary)) AS heikin FROM shain WHERE taishoku_on IS NULL;
ninzuheikin
16547753

1

退職者 2 人が除かれ、人数も平均も変わります。「誰を数えているのか」を毎回確かめてください。

⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表

絞り込まない場合と見比べる
SELECT count(*) AS ninzu, round(avg(salary)) AS heikin FROM shain;
ninzuheikin
18542003

1

同じ問いに見えて、母集団が違えば答えも違います。集計値を報告するときは、母集団を必ず添えてください。

⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表

⚠️ WHERE に集約関数は書けない
SELECT count(*) FROM shain WHERE avg(salary) > 500000;
ERROR: aggregate functions are not allowed in WHERE

WHERE は集約より先に評価されるため、この時点で平均という値は存在しません。集計結果への条件は HAVING に書きます。

05最大・最小は「その行」を返さない

結論: `max(salary)` が返すのは金額だけで、その人が誰かは分かりません。

集約関数は値を返す道具なので、「最高額の社員は誰か」を知りたいなら、求めた値と等しい行を探し直す必要があります。

SELECT name, max(salary) FROM shain と書きたくなりますが、これはエラーになります。name は 1 つに定まらないためです。

⚠️ 名前と最大値を並べて書くとエラー
SELECT name, max(salary) FROM shain;
ERROR: column "shain.name" must appear in the GROUP BY clause or be used in an aggregate function

18 行を 1 行にまとめる以上、name をどの行から取ればよいか決まりません。だからエラーになります。

最大値と等しい行を探す
SELECT name, salary FROM shain WHERE salary = (SELECT max(salary) FROM shain);
namesalary
鈴木980000

1

先に最大値を求め、その値と等しい行を取り出します。同額が複数いれば全員返ります。

自分で打ってみる

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

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

解いてみる

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

関連するトピック

  • GROUP BY 句まとめる単位を指定すると、集約関数はその単位ごとに計算されます。
  • HAVING 句集計した結果に条件を付けるときは WHERE ではなく HAVING です。
  • IS NULL集約関数が NULL を無視することが、この章の落とし穴の正体です。

根拠(一次情報)