SQL GROUP BY と集約関数: データを要約しよう
Published: 2026-04-08
GROUP BY は、1 つ以上の列で同じ値を持つ複数行を 1 行のサマリー行にまとめます。COUNT、SUM、AVG、MIN、MAX といった集約関数と組み合わせることで、ほぼすべてのレポート・分析クエリの基盤になります。GROUP BY は SQLite、PostgreSQL、MySQL、SQL Server、Oracle を含むすべての SQL データベースでサポートされています。
サンプルデータ
以降の例では 2 つのテーブルを使います。
employees テーブル:
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
orders テーブル:
| id | customer | product | category | quantity | unit_price | order_date | status |
|---|---|---|---|---|---|---|---|
| 1 | Acme Corp | Laptop Pro | Electronics | 3 | 1299.99 | 2026-01-05 | delivered |
| 2 | Acme Corp | Wireless Mouse | Electronics | 10 | 29.99 | 2026-01-12 | delivered |
| 3 | Globex | Office Chair | Furniture | 5 | 249.99 | 2026-01-18 | delivered |
| 4 | Globex | Standing Desk | Furniture | 2 | 599.99 | 2026-01-20 | shipped |
| 5 | Initech | Notebook Pack | Stationery | 20 | 12.99 | 2026-02-03 | delivered |
| 6 | Initech | Pen Set | Stationery | 15 | 8.99 | 2026-02-07 | delivered |
| 7 | Acme Corp | Monitor 27" | Electronics | 4 | 399.99 | 2026-02-14 | delivered |
| 8 | Umbrella Ltd | Laptop Pro | Electronics | 2 | 1299.99 | 2026-02-20 | cancelled |
| 9 | Umbrella Ltd | Desk Lamp | Furniture | 8 | 39.99 | 2026-03-01 | delivered |
| 10 | Globex | Wireless Mouse | Electronics | 12 | 29.99 | 2026-03-05 | shipped |
| 11 | Initech | Monitor 27" | Electronics | 1 | 399.99 | 2026-03-10 | delivered |
| 12 | Acme Corp | Standing Desk | Furniture | 1 | 599.99 | 2026-03-15 | processing |
GROUP BY の構文
SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition
GROUP BY column1
HAVING aggregate_condition
ORDER BY column1;
- GROUP BY — 1 つ以上の列。これらの列の値の組み合わせごとに 1 つのグループが定義される
- 集約関数 — グループ内のすべての行から 1 つの値を計算する
- HAVING — グループをフィルタする(WHERE に似ているが、グループ化後の結果に対して使う)
- 集約関数の外にある SELECT の列は、すべて GROUP BY に含める必要がある
SQL の実行順序
SQL の句はある順番で書きますが、別の順番で実行されます。これを理解すると、なぜ HAVING では集約関数を参照できるのに WHERE ではできないのか、またなぜ HAVING 句で SELECT 句のエイリアスを使えないのかが分かります。
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
| Step | Clause | 何が起こるか |
|---|---|---|
| 1 | FROM | テーブルを読み込み、JOIN を評価する |
| 2 | WHERE | 個々の行をフィルタする — ここでは集約関数は使えない |
| 3 | GROUP BY | 残った行をグループにまとめる |
| 4 | HAVING | グループをフィルタする — ここでは集約関数が使える |
| 5 | SELECT | 出力列とエイリアスを計算する |
| 6 | ORDER BY | 最終的な結果セットをソートする |
そのため、WHERE status = 'delivered' はグループ化の前に実行され(高速な行レベルのフィルタ)、HAVING COUNT(*) > 2 はグループ化の後に実行されます(グループ全体をフィルタ)。
COUNT()
COUNT(*) はグループ内のすべての行を数えます。COUNT(column) はその列の NULL でない値だけを数えます。
例: 部署ごとの従業員数
例: 顧客ごとの注文数
例: 配送済み vs その他ステータスの件数
CASE 式と組み合わせた COUNT で、条件に合う注文だけを数えます。
SUM()
SUM(column) は、数値列についてグループ内のすべての値を合計します。
例: 部署ごとの給与合計
例: カテゴリごとの売上合計
売上は quantity * unit_price で計算します。
例: 顧客 × カテゴリ別の売上
複数列でグループ化して、売上をさらに細かく分解します。
AVG()
AVG(column) は、グループ内の NULL でない値の算術平均を返します。
例: 部署ごとの平均給与
例: 顧客ごとの平均注文額
MIN() と MAX()
MIN(column) と MAX(column) は、グループ内の最小値と最大値を返します。
例: 部署ごとの給与レンジ
例: 顧客ごとの最初と最後の注文日
複数の集約関数を組み合わせる
1 つのクエリの中で、任意の数の集約関数を組み合わせて使えます。
例: 部署のフルサマリー
例: 顧客ごとの注文サマリー
HAVING
HAVING は集約後のグループをフィルタします。グループ化された結果に対する WHERE のようなものです。
例: 従業員が 3 人超いる部署
例: 合計売上が 2000 超の顧客
例: 平均注文額が 500 未満のカテゴリ
WHERE と HAVING の違い
WHERE はグループ化の「前」に個々の行をフィルタします。HAVING は集約の「後」にグループをフィルタします。
例: 顧客ごとの「配送済み注文のみ」の売上
WHERE でグループ化前に行を除外し、その後 HAVING で結果をフィルタします。
JOIN と GROUP BY
複数テーブルにまたがるデータも、まず JOIN してからグループ化することで集計できます。
例: 従業員テーブルとの JOIN による部署別売上
例: カテゴリ × 月別の注文数と売上
式と GROUP BY
GROUP BY では、生の列だけでなく計算式でもグループ化できます。
例: 年ごとの採用人数
例: 月別の注文
COUNT(DISTINCT)
COUNT(DISTINCT column) は、グループ内のユニークな値の数を数えます。
例: 顧客ごとのユニークな購入商品数
複数条件でグループをフィルタする
HAVING には AND / OR を含む任意のブール式を使えます。
例: 高価値かつアクティブな顧客
注文が 2 件超あり、かつ合計売上が 1000 超の顧客を探します。
実践例: 売上レポート
ここまでの内容を組み合わせて、売上サマリレポートを作ってみます。
例: カテゴリ別パフォーマンスレポート
例: 売上トップの商品
GROUP BY とウィンドウ関数の違い
GROUP BY は行をグループごとに 1 行へと「潰し」ます。ウィンドウ関数はすべての行を保持したまま、グループ単位の計算結果を列として追加します。詳しくは SQL ウィンドウ関数チュートリアル を参照してください。
例: GROUP BY は部署ごとに 1 行を返す
例: ウィンドウ関数はすべての行を保持する
GROUP BY の代表的なユースケース
- レポートダッシュボード — カテゴリや期間ごとに合計・平均・件数を集計
- 人員分析 — 部署やグループごとの従業員・ユーザー・レコード数をカウント
- 売上内訳 — 商品・地域・顧客ごとの売上合計や平均を算出
- トレンド分析 — 月別・年別にグループ化して時系列パターンを把握
- グループ内トップ N — サブクエリや CTE と組み合わせて、各グループのトップパフォーマーを抽出
- データ検証 — グループごとの重複や NULL の件数を確認
- コホート分析 — 登録日や行動セグメントごとにユーザーをグループ化
パフォーマンスのコツ
- GROUP BY 列にインデックスを張る — グループ化やソートのステップを高速化できる
- WHERE で早めにフィルタする — HAVING で後からフィルタするより、GROUP BY 前に行数を減らした方が速い
- SELECT * を避ける — 本当に必要な列だけを選択する。集約処理は列数が少ないほど有利
- カバリングインデックスを使う — GROUP BY 列と集約対象列の両方を含むインデックスがあれば、全表走査を避けられる場合がある
- マテリアライズドビューを検討する — 大規模テーブルで頻繁に同じ集約を行う場合、一部の DB では事前計算されたサマリをサポートしている
まとめ
- GROUP BY は、同じ列値を持つ行をユニークな組み合わせごとに 1 行のサマリー行へとまとめる
- COUNT(*) はすべての行を数え、COUNT(column) は NULL 以外を数え、COUNT(DISTINCT column) はユニークな値の数を数える
- SUM は数値を合計し、AVG は平均を計算し、MIN / MAX は最小値・最大値を求める
- SELECT の各列は、GROUP BY に含めるか、集約関数で包む必要がある
- WHERE はグループ化前に行をフィルタし、HAVING は集約後にグループをフィルタする
SUBSTR(date, 1, 7)のように、式を使って GROUP BY することもできる(月単位のグループ化など)- グループ化前にテーブルを JOIN すれば、複数ソースのデータを 1 つのレポートにまとめられる
- 個々の行を残したままグループ単位の指標が欲しい場合は、GROUP BY ではなくウィンドウ関数を使う
GROUP BY を JOIN、CASE 式、HAVING 句と組み合わせて、必要なデータ分析レポートを自在に組み立ててみてください。