SQL練習室
← SQLガイド

SQLのGROUP BYとは?複数列・COUNT・NULLの集計と使い方

GROUP BYは、同じ値の行をまとめて、部署別の人数や顧客別の売上を求める構文です。最初に「結果の1行は何を表すか」を決めると、集計のズレを防げます。基本の件数集計から、NULL・重複・条件付き集計の使い分けまで順に確認します。

GROUP BYの基本構文

sql
SELECT department, COUNT(*)
FROM employees
GROUP BY department;

GROUP BYの基本:部署ごとにCOUNTで人数を数える

GROUP BY departmentで部署の値が同じ行を1グループにし、COUNT(*)で各グループの行数を数えます。Salesが2行、Supportが1行なら結果は2行で、人数はそれぞれ2と1です。集計単位以外の明細列をそのままSELECTすると、どの行の値を出すか決まらないため原則エラーになります。

sql
SELECT department, COUNT(*)
FROM employees
GROUP BY department;

SUMで合計を求める

SUM(salary)は部署内の給与を足し合わせます。人数ではなく人件費を知りたいときに使います。SUMの対象列は集約関数の中にあるためGROUP BYへ追加しません。salaryもGROUP BYに書くと、部署と給与の組み合わせ別になってしまいます。

sql
SELECT department, SUM(salary)
FROM employees
GROUP BY department;

AVGで平均を求める:NULLを0とみなさない

AVGNULLでない値だけで平均を計算します。給与が100、200、NULLなら平均は150で、100にはなりません。未入力を0として扱う業務要件ならAVG(COALESCE(salary, 0))ですが、不明な値を0とみなしてよいかを先に確認しましょう。

sql
SELECT department, AVG(salary)
FROM employees
GROUP BY department;

MIN・MAXで最小値と最大値を調べる

MINMAXは値の下限と上限を返します。給与の幅や最初・最後の注文日を調べるときに使えます。ただしMAX(salary)だけでは最高給与の社員名は得られません。その明細も必要なら、サブクエリやウィンドウ関数で対象行を選びます。

sql
SELECT department, MIN(salary), MAX(salary)
FROM employees
GROUP BY department;

複数列のGROUP BY:値の組み合わせで集計する

GROUP BY department, activeは「部署×在籍状態」ごとに1行を作ります。列を増やすほど結果の粒度が細かくなるため、部署別人数より行数が増えることがあります。GROUP BYに並び順の保証はなく、表示順が必要ならORDER BYを追加します。

sql
SELECT department, active, COUNT(*)
FROM employees
GROUP BY department, active
ORDER BY department, active;

COUNT(*)とCOUNT(列)の違い:NULLの件数

COUNT(*)は全行、COUNT(salary)は給与がNULLでない行を数えます。差を取れば未入力件数になります。一方、集計キーのdepartmentがNULLの行は、GROUP BYでは1つのグループにまとまります。NULLを集計から除きたい場合は、WHEREで明示的に除外してください。

sql
SELECT department, COUNT(*), COUNT(salary)
FROM employees
GROUP BY department;

COUNT DISTINCTで重複しない値を数える

COUNT(DISTINCT salary)は、部署内で何種類の給与があるかを数えます。人数ではなく種類数を求める集計です。同額の人が複数いても1種類で、NULLは数えません。顧客IDへ置き換えれば、購入回数ではなく購入者数を求められます。

sql
SELECT department, COUNT(DISTINCT salary)
FROM employees
GROUP BY department;

FILTERで条件付き集計を同じ行に並べる

FILTERは、その集約関数に渡す行だけを絞ります。在籍者数と全社員数を横に並べたいとき、WHEREで在籍者だけにすると全社員数まで減ってしまいます。PostgreSQLでは次のように書けます。他のDBへ移植する際はFILTERの対応状況を確認してください。

sql
SELECT
  department,
  COUNT(*),
  COUNT(*) FILTER (WHERE active) AS active_count
FROM employees
GROUP BY department;

WHEREとGROUP BY:集計対象を先に絞る

在籍者だけの部署別人数なら、WHERE active = trueで対象を決めてから集計します。考え方はFROMWHEREGROUP BYHAVINGORDER BYです。これは結果を理解するための論理的な順序で、DB内部の物理的な実行順序とは区別します。集計した人数自体で絞る方法はHAVINGガイドで扱います。

sql
SELECT department, COUNT(*)
FROM employees
WHERE active = true
GROUP BY department
ORDER BY department;

GROUP BYでよくある間違い

0件の集計と「存在しないグループ」は別

GROUP BYなしのCOUNTは入力0件でも0を返しますが、GROUP BYありではグループ自体がなく結果は0行です。SUMは入力0件でNULLになります。存在しない部署を0人で表示するには部署マスタを起点としたLEFT JOINなどが必要で、COALESCEだけでは行を作れません。

sql
SELECT COUNT(*), SUM(salary)
FROM employees
WHERE false;

SELECT department, COUNT(*)
FROM employees
WHERE false
GROUP BY department;

実際に1問解いてみる

GROUP BY・COUNT(*)

employeesの部署ごとの人数を、departmentCOUNT(*)の順で取得してください。行順は問いません。

テーブル

読み込み中…

実行結果

関連構文

GROUP BYについてよくある質問

GROUP BYとDISTINCTはどう使い分けますか?
重複のない一覧を作るだけならDISTINCT、グループ別に件数や合計を計算するならGROUP BYを使います。どちらも結果の並び順を保証しません。
GROUP BYにない列をSELECTするとエラーになるのはなぜですか?
1グループに複数の値があり、返す値が決まらないためです。集計キーへ追加するか、MINなどで選び方を指定します。PostgreSQLでは同じ基本テーブルの主キーをグループ化すると、主キーに従属する列を省略できる場合があります。
GROUP BYとPARTITION BYの違いは?
GROUP BYはグループを1行にまとめます。ウィンドウ関数のPARTITION BYは明細行を残して計算範囲を分けます。社員一覧に部署平均を添えるなら後者です。

参考文献

内容はPostgreSQL公式ドキュメントを参照し、初めて学ぶ人向けに日本語で整理しています。