GROUP BY 句の使い方
shain社員
| busho_id | salary |
|---|---|
| 1 | 980000 |
| 2 | 720000 |
| 2 | 530000 |
| 2 | 520000 |
| … | |
GROUP BYまとめた結果
| busho_id | avg(salary) |
|---|---|
| 1 | 980000 |
| 2 | 553763 |
| 3 | 532750 |
| … | |
GROUP BY busho_id
- 社員 16 行 が、6 行にまとまります(同じ busho_id の行が 1 行になる)。
- avg(salary)はまとまりごとに計算した値で、元の表には無い列です。
この図では、部署が決まっていない社員を除いて数えています(NULL のまとまりについては後半で扱います)。
GROUP BY は「同じ値の行をひとまとめにして、まとまりごとに 1 行を返す」書き方です。 まとめた後は行が減るので、個々の行の情報は取り出せなくなります。ここが最初のつまずきどころです。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01「まとめる単位」を指定する
結論: `GROUP BY 列` と書くと、その列の値が同じ行がひとまとめになります。
返る行数は「まとまりの数」です。社員を部署ごとにまとめれば、返るのは部署の数だけ。何十人いても、部署が 6 つなら 6 行です。
まとまりごとに何を計算するかは SELECT に書きます。人数なら count(*)、平均給与なら avg(salary) です。
SELECT busho_id, count(*), avg(salary)まとまりごとに計算するFROM shainどの表を見るかWHERE taishoku_on IS NULLまとめる前に行を絞るGROUP BY busho_id何を単位にまとめるかHAVING count(*) >= 2まとめた後に絞るORDER BY busho_id並び順
WHERE は「まとめる前」、HAVING は「まとめた後」。この順番を押さえると使い分けで迷いません。
SELECT busho_id, count(*) AS ninzu
FROM shain
GROUP BY busho_id
ORDER BY busho_id;| busho_id | ninzu |
|---|---|
| 1 | 1 |
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 7 | 2 |
| NULL | 2 |
(7 行)
02⚠️ よくある間違い:SELECT に書ける列は決まっている
`GROUP BY` に書いた列と、集約関数だけが SELECT に書けます。
それ以外の列を書くとエラーになります。理由は単純で、まとまりの中で 1 つに決まらないからです。開発部に 4 人いるとき、「その部署の name」は 4 つあって 1 つに決められません。
必要なら、その列も GROUP BY に足すか、max() などで 1 つに決めます。
SELECT busho_id, name, count(*)
FROM shain
GROUP BY busho_id;「name は GROUP BY にも集約関数にも入っていない」と言われます。エラーメッセージが原因をそのまま説明しています。
03⚠️ よくある間違い:count(*) と count(列) は別物
count(*) は行の数を数えます。count(列) はその列が NULL でない行の数を数えます。
値が入っていない行があると数が変わります。「人数」を数えたいのか「値が入っている行数」を数えたいのかで、使い分けてください。
この違いは外部結合と組み合わせたときに特に効きます(相手がいない行を 1 と数えてしまう)。
SELECT count(*) AS zenin, count(taishoku_on) AS taishokusha
FROM shain;| zenin | taishokusha |
|---|---|
| 18 | 2 |
(1 行)
同じ表でも数が違います。count(taishoku_on) は NULL を数えていません。
04絞り込みは「まとめる前」か「まとめた後」か
まとめる前に行を減らすのが `WHERE`、まとめた後に減らすのが `HAVING` です。
「在籍者だけで平均を出す」なら、まとめる前に退職者を外すので WHERE です。「2 人以上いる部署だけ出す」なら、人数はまとめないと分からないので HAVING です。
両方書けます。順番も上のとおり WHERE → GROUP BY → HAVING です。
SELECT busho_id, count(*) AS ninzu, avg(salary)::numeric(10,0) AS heikin
FROM shain
WHERE taishoku_on IS NULL
GROUP BY busho_id
HAVING count(*) >= 2
ORDER BY busho_id;| busho_id | ninzu | heikin |
|---|---|---|
| 2 | 4 | 553763 |
| 3 | 3 | 550333 |
| 4 | 2 | 552500 |
| 5 | 2 | 539000 |
| 7 | 2 | 451500 |
| NULL | 2 | 416000 |
(6 行)
05NULL も 1 つのまとまりになる
GROUP BY した列が NULL の行は、「NULL というまとまり」として 1 行にまとまります。捨てられるわけではありません。
WHERE では NULL の行が落ちるのに、GROUP BY では残ります。挙動が逆なので混乱しやすいところです。
出したくない場合は WHERE 列 IS NOT NULL で先に外します。
SELECT busho_id, count(*) AS ninzu
FROM shain
GROUP BY busho_id
ORDER BY busho_id NULLS LAST;| busho_id | ninzu |
|---|---|
| 1 | 1 |
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 7 | 2 |
| NULL | 2 |
(7 行)
busho_id が空の行が 1 行あります。これが「部署が決まっていない社員」のまとまりです。
⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表
06並び替えたいときは ORDER BY を別に書く
GROUP BY はまとめるだけで、並び順は保証しません。「たまたま昇順に見える」ことはありますが、当てにしてはいけません。
並び順が必要なら ORDER BY を必ず書きます。集約した値でも並べられます。
SELECT で付けた別名は、ORDER BY では使えます(WHERE では使えません)。評価される順番が SELECT → ORDER BY だからです。
SELECT busho_id, count(*) AS ninzu
FROM shain
GROUP BY busho_id
ORDER BY ninzu DESC, busho_id;| busho_id | ninzu |
|---|---|
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 7 | 2 |
| NULL | 2 |
| 1 | 1 |
(7 行)
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- 集約関数(練習問題)まとまりごとに何を計算するか(COUNT / SUM / AVG / MIN / MAX)。
- HAVING 句まとめた結果に条件を付けます。
- WHERE 句まとめる前に行を絞ります。順番の違いが使い分けの決め手です。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 問い合わせ — GROUP BY と HAVING 句一次情報・確認 2026-07-28
- PostgreSQL 18 マニュアル: 集約関数一次情報・確認 2026-07-28
- SQLite: SELECT(GROUP BY clause)一次情報・確認 2026-07-28