集約関数(COUNT / SUM / AVG / MIN / MAX)の使い方
shain社員
| busho_id | salary |
|---|---|
| 1 | 980000 |
| 2 | 720000 |
| 2 | 530000 |
| 2 | 520000 |
| … | |
集約まとめた結果
| busho_id | count(salary) |
|---|---|
| 1 | 1 |
| 2 | 4 |
| 3 | 4 |
| … | |
GROUP BY busho_id
- 社員 16 行 が、6 行にまとまります(同じ busho_id の行が 1 行になる)。
- count(salary)はまとまりごとに計算した値で、元の表には無い列です。
ここでは部署ごとにまとめていますが、まとめる単位を指定しなければ「表全体で 1 つ」になります。
集約関数は、たくさんの行を 1 つの値にまとめる道具です。いちばんの落とし穴は NULL の扱いで、count(*) は行を数え、count(列) は値のある行だけを数えます。sum も avg も NULL を無視するため、平均の分母が思っているものと違うことがあります。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01表全体を 1 行にまとめる
結論: 集約関数を書くと、結果は 1 行になります。
GROUP BY を書かなければ、表のすべての行が 1 つのまとまりとして扱われます。18 行あった社員表が、1 行の集計結果になります。
何行あっても結果が 1 行になるので、「絞り込みが効いているか」は件数を一緒に出して確かめるのが確実です。
SELECT count(*) AS ninzu,行の数max(salary) AS saikou,最大値min(salary) AS saitei最小値FROM shainどの表を見るか
集約関数はいくつでも並べられます。すべて同じ行の集まりに対して計算されます。
SELECT count(*) AS ninzu, max(salary) AS saikou, min(salary) AS saitei FROM shain;| ninzu | saikou | saitei |
|---|---|---|
| 18 | 980000 | 398000 |
(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;| zen | shozoku_ari | shozoku_nashi |
|---|---|---|
| 18 | 16 | 2 |
(1 行)
差の 2 人が「部署が決まっていない社員」です。count(列) は NULL を数えません。
SELECT name, busho_id FROM shain WHERE busho_id IS NULL ORDER BY shain_id;| name | busho_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;| goukei | gyosu | atai_ari | heikin |
|---|---|---|---|
| 9756050 | 18 | 18 | 542002.777777777778 |
(1 行)
salary には NULL が無いので、ここでは行数と値のある行数が同じです。だから avg は「合計 ÷ 行数」と一致します。
SELECT count(*) AS gyosu, count(taishoku_on) AS taishoku_ari, avg(salary) AS zenin_heikin FROM shain;| gyosu | taishoku_ari | zenin_heikin |
|---|---|---|
| 18 | 2 | 542002.777777777778 |
(1 行)
taishoku_on は退職者にしか入っていないため、count(taishoku_on) は 2 になります。同じ表でも、どの列を数えるかで結果が変わります。
04絞り込んでから集計する
結論: `WHERE` は集計より先に効きます。
集約関数が見るのは「WHERE で絞ったあとの行」です。したがって「在籍者だけの平均」は WHERE で退職者を落としてから avg を取ります。
順番は FROM → WHERE → 集約です。集約した結果に条件を付けたいときは `HAVING` を使います(WHERE には書けません)。
SELECT count(*) AS ninzu, round(avg(salary)) AS heikin FROM shain WHERE taishoku_on IS NULL;| ninzu | heikin |
|---|---|
| 16 | 547753 |
(1 行)
退職者 2 人が除かれ、人数も平均も変わります。「誰を数えているのか」を毎回確かめてください。
⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表
SELECT count(*) AS ninzu, round(avg(salary)) AS heikin FROM shain;| ninzu | heikin |
|---|---|
| 18 | 542003 |
(1 行)
同じ問いに見えて、母集団が違えば答えも違います。集計値を報告するときは、母集団を必ず添えてください。
⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表
SELECT count(*) FROM shain WHERE avg(salary) > 500000;WHERE は集約より先に評価されるため、この時点で平均という値は存在しません。集計結果への条件は HAVING に書きます。
05最大・最小は「その行」を返さない
結論: `max(salary)` が返すのは金額だけで、その人が誰かは分かりません。
集約関数は値を返す道具なので、「最高額の社員は誰か」を知りたいなら、求めた値と等しい行を探し直す必要があります。
SELECT name, max(salary) FROM shain と書きたくなりますが、これはエラーになります。name は 1 つに定まらないためです。
SELECT name, max(salary) FROM shain;18 行を 1 行にまとめる以上、name をどの行から取ればよいか決まりません。だからエラーになります。
SELECT name, salary FROM shain WHERE salary = (SELECT max(salary) FROM shain);| name | salary |
|---|---|
| 鈴木 | 980000 |
(1 行)
先に最大値を求め、その値と等しい行を取り出します。同額が複数いれば全員返ります。
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- GROUP BY 句まとめる単位を指定すると、集約関数はその単位ごとに計算されます。
- HAVING 句集計した結果に条件を付けるときは WHERE ではなく HAVING です。
- IS NULL集約関数が NULL を無視することが、この章の落とし穴の正体です。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 集約関数一次情報・確認 2026-08-02
- PostgreSQL 18 マニュアル: 集約式一次情報・確認 2026-08-02