SQL GROUP BY と集約関数: データを要約しよう

Published: 2026-04-08

GROUP BY は、1 つ以上の列で同じ値を持つ複数行を 1 行のサマリー行にまとめます。COUNTSUMAVGMINMAX といった集約関数と組み合わせることで、ほぼすべてのレポート・分析クエリの基盤になります。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
DbGate Lite でデータセットを探索する

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 の件数を確認
  • コホート分析 — 登録日や行動セグメントごとにユーザーをグループ化

パフォーマンスのコツ

  1. GROUP BY 列にインデックスを張る — グループ化やソートのステップを高速化できる
  2. WHERE で早めにフィルタする — HAVING で後からフィルタするより、GROUP BY 前に行数を減らした方が速い
  3. SELECT * を避ける — 本当に必要な列だけを選択する。集約処理は列数が少ないほど有利
  4. カバリングインデックスを使う — GROUP BY 列と集約対象列の両方を含むインデックスがあれば、全表走査を避けられる場合がある
  5. マテリアライズドビューを検討する — 大規模テーブルで頻繁に同じ集約を行う場合、一部の 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 句と組み合わせて、必要なデータ分析レポートを自在に組み立ててみてください。