「売上を月ごと・商品ごとに集計したい」
「ユーザーの年齢と性別ごとに平均値を出したい」
そんなときに役立つのが、GROUP BY
句を複数条件で使うテクニックです。
この記事では、SQLのGROUP BYで複数列を指定する方法と実用的な集計例、注意点まで、初心者でもすぐに使える形でわかりやすく解説します。
GROUP BYの基本的な仕組み

GROUP BYとは何か
GROUP BY
は、データを特定の列の値でグループ分けし、集計処理(COUNT、SUM、AVGなど)を適用するSQL句です。
基本的な使い方
基本構文
SELECT 列名, 集計関数(列名)
FROM テーブル名
GROUP BY 列名;
簡単な例
SELECT 年, COUNT(*) AS 件数
FROM sales
GROUP BY 年;
この例では、sales
テーブルを「年ごと」にグループ化して件数を数えます。
GROUP BYの動作イメージ
元のデータ
年 | 商品名 | 売上金額 |
---|---|---|
2023 | りんご | 1000 |
2023 | みかん | 1500 |
2024 | りんご | 1200 |
2024 | みかん | 1800 |
GROUP BY 年 の結果
年 | 件数 |
---|---|
2023 | 2 |
2024 | 2 |
よく使う集計関数
基本的な集計関数
- COUNT():行数を数える
- SUM():合計値を計算
- AVG():平均値を計算
- MAX():最大値を取得
- MIN():最小値を取得
実用例
-- 年ごとの売上合計
SELECT 年, SUM(売上金額) AS 年間売上
FROM sales
GROUP BY 年;
-- 年ごとの平均売上
SELECT 年, AVG(売上金額) AS 平均売上
FROM sales
GROUP BY 年;
GROUP BYの基本を理解したところで、次は複数の条件を組み合わせる方法を見てみましょう。
複数条件でGROUP BYを使う方法

基本構文と考え方
複数列でのGROUP BY構文
SELECT 列1, 列2, 集計関数(列名)
FROM テーブル名
GROUP BY 列1, 列2;
重要なポイント
- GROUP BYに指定した列は、SELECT句にも含める必要がある
- 複数列の組み合わせごとにグループが作られる
- 左から右の順序でグループ化される
実践的な例
年と商品名の組み合わせで集計
SELECT 年, 商品名, SUM(売上金額) AS 売上合計
FROM sales
GROUP BY 年, 商品名;
結果のイメージ
年 | 商品名 | 売上合計 |
---|---|---|
2023 | りんご | 15000 |
2023 | みかん | 12000 |
2024 | りんご | 18000 |
2024 | みかん | 14000 |
より複雑な例
地域・年・商品の3つの条件で集計
SELECT 地域, 年, 商品名,
SUM(売上金額) AS 売上合計,
COUNT(*) AS 販売回数,
AVG(売上金額) AS 平均売上
FROM sales
GROUP BY 地域, 年, 商品名;
部門・月・担当者での集計
SELECT 部門,
EXTRACT(MONTH FROM 売上日) AS 月,
担当者,
SUM(売上金額) AS 月間売上,
COUNT(DISTINCT 顧客ID) AS 顧客数
FROM sales
GROUP BY 部門, EXTRACT(MONTH FROM 売上日), 担当者;
GROUP BYの順序の影響
順序による結果の違い
-- パターン1
GROUP BY 年, 商品名
-- パターン2
GROUP BY 商品名, 年
どちらも同じ集計結果になりますが、内部的な処理順序が異なります。一般的には、ユニークな値が少ない列を先に指定するとパフォーマンスが向上することがあります。
日付関数との組み合わせ
月ごとの集計
SELECT YEAR(売上日) AS 年,
MONTH(売上日) AS 月,
SUM(売上金額) AS 月間売上
FROM sales
GROUP BY YEAR(売上日), MONTH(売上日);
四半期ごとの集計
SELECT YEAR(売上日) AS 年,
QUARTER(売上日) AS 四半期,
SUM(売上金額) AS 四半期売上
FROM sales
GROUP BY YEAR(売上日), QUARTER(売上日);
複数条件でのGROUP BYをマスターすると、詳細な分析ができるようになります。次は、よく間違いやすいORDER BYとの違いについて説明します。
ORDER BYとGROUP BYの違いと使い分け

基本的な違い
GROUP BYの役割
- 集計単位を決める
- データをグループに分けて計算する
- 結果の行数を減らす
ORDER BYの役割
- 表示順序を決める
- データの並び順を制御する
- 行数は変わらない
組み合わせて使う例
GROUP BYとORDER BYを両方使用
SELECT 年, 商品名, SUM(売上金額) AS 売上合計
FROM sales
GROUP BY 年, 商品名
ORDER BY 年 ASC, 売上合計 DESC;
この例では:
- 年と商品名でグループ化して集計
- 年の昇順、売上合計の降順で並び替え
実用的な並び替え例
売上上位の商品を表示
SELECT 商品名, SUM(売上金額) AS 売上合計
FROM sales
GROUP BY 商品名
ORDER BY 売上合計 DESC
LIMIT 10;
地域別・月別の売上を時系列順に表示
SELECT 地域,
YEAR(売上日) AS 年,
MONTH(売上日) AS 月,
SUM(売上金額) AS 月間売上
FROM sales
GROUP BY 地域, YEAR(売上日), MONTH(売上日)
ORDER BY 地域, 年, 月;
よくある間違い
間違った例
-- エラー:ORDER BYにGROUP BYにない列を指定
SELECT 商品名, SUM(売上金額)
FROM sales
GROUP BY 商品名
ORDER BY 売上日; -- 売上日はGROUP BYで消失している
正しい例
-- 集計値や集計に使用した列での並び替え
SELECT 商品名, SUM(売上金額) AS 売上合計
FROM sales
GROUP BY 商品名
ORDER BY 売上合計 DESC;
パフォーマンスを考慮した書き方
効率的な順序
-- 良い例:絞り込み → グループ化 → 並び替え
SELECT 地域, 商品名, SUM(売上金額) AS 売上合計
FROM sales
WHERE 売上日 >= '2024-01-01' -- 先にデータを絞り込み
GROUP BY 地域, 商品名
ORDER BY 地域, 売上合計 DESC;
ORDER BYは結果の見やすさを大きく左右します。次は、GROUP BYと組み合わせて使うWHERE句について説明します。
WHEREとGROUP BYの関係と実用例

基本的な処理順序
SQLの実行順序
- FROM:テーブルを指定
- WHERE:行レベルでフィルタリング
- GROUP BY:グループ化
- HAVING:グループレベルでフィルタリング
- SELECT:列を選択
- ORDER BY:並び替え
WHEREを使った事前フィルタリング
特定期間のデータのみを集計
SELECT 地域, 商品名, SUM(売上金額) AS 売上合計
FROM sales
WHERE 売上日 BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY 地域, 商品名;
特定の条件を満たすデータのみを集計
SELECT 性別, 年齢層, COUNT(*) AS 人数, AVG(購入金額) AS 平均購入金額
FROM customers
WHERE 地域 = '東京' AND 会員ランク IN ('ゴールド', 'プラチナ')
GROUP BY 性別, 年齢層;
複数条件でのフィルタリング
売上金額が一定以上の取引のみを集計
SELECT 担当者,
MONTH(売上日) AS 月,
COUNT(*) AS 取引件数,
SUM(売上金額) AS 月間売上
FROM sales
WHERE 売上金額 >= 10000 -- 1万円以上の取引のみ
AND 売上日 >= '2024-01-01' -- 2024年以降のみ
GROUP BY 担当者, MONTH(売上日);
NULL値を除外した集計
SELECT 商品カテゴリ,
ブランド,
COUNT(*) AS 商品数,
AVG(価格) AS 平均価格
FROM products
WHERE 商品カテゴリ IS NOT NULL
AND ブランド IS NOT NULL
AND 価格 > 0
GROUP BY 商品カテゴリ, ブランド;
日付条件との組み合わせ
直近3ヶ月のデータを月別に集計
SELECT YEAR(売上日) AS 年,
MONTH(売上日) AS 月,
COUNT(*) AS 取引件数,
SUM(売上金額) AS 月間売上
FROM sales
WHERE 売上日 >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY YEAR(売上日), MONTH(売上日)
ORDER BY 年, 月;
平日のみの売上を集計
SELECT 店舗名,
DAYNAME(売上日) AS 曜日,
AVG(売上金額) AS 平均売上
FROM sales
WHERE WEEKDAY(売上日) BETWEEN 0 AND 4 -- 月曜~金曜
GROUP BY 店舗名, DAYNAME(売上日);
文字列条件との組み合わせ
特定の商品名パターンで集計
SELECT LEFT(商品名, 3) AS 商品プレフィックス,
COUNT(*) AS 商品数,
AVG(価格) AS 平均価格
FROM products
WHERE 商品名 LIKE '%セット%' -- セットを含む商品のみ
GROUP BY LEFT(商品名, 3)
HAVING COUNT(*) >= 5; -- 5個以上の商品があるグループのみ
WHEREを適切に使うことで、必要なデータだけを効率的に集計できます。次は、グループレベルでの条件指定ができるHAVING句について説明します。
HAVINGでグループに条件をつける方法
HAVINGの基本的な役割
WHEREとHAVINGの違い
- WHERE:GROUP BYの前に適用される行レベルのフィルター
- HAVING:GROUP BYの後に適用されるグループレベルのフィルター
基本的な使い方
売上が一定以上の商品のみ表示
SELECT 商品名, SUM(売上金額) AS 合計売上
FROM sales
GROUP BY 商品名
HAVING SUM(売上金額) > 100000;
一定数以上売れた商品のみ表示
SELECT 商品名,
COUNT(*) AS 販売回数,
SUM(売上金額) AS 合計売上
FROM sales
GROUP BY 商品名
HAVING COUNT(*) >= 10;
複雑なHAVING条件
複数の集計条件を組み合わせ
SELECT 地域, 商品カテゴリ,
COUNT(*) AS 販売回数,
SUM(売上金額) AS 合計売上,
AVG(売上金額) AS 平均売上
FROM sales
GROUP BY 地域, 商品カテゴリ
HAVING COUNT(*) >= 20 -- 20回以上販売
AND SUM(売上金額) > 500000 -- 合計売上50万円以上
AND AVG(売上金額) > 15000; -- 平均売上1万5千円以上
特定の割合や範囲での条件
SELECT 担当者,
COUNT(*) AS 取引件数,
SUM(売上金額) AS 合計売上,
AVG(売上金額) AS 平均売上
FROM sales
GROUP BY 担当者
HAVING AVG(売上金額) BETWEEN 20000 AND 100000 -- 平均売上が2-10万円
AND COUNT(*) > (SELECT AVG(cnt) FROM (
SELECT COUNT(*) as cnt FROM sales GROUP BY 担当者
) as temp); -- 平均取引件数以上
時系列データでのHAVING活用
継続的に売上がある商品を特定
SELECT 商品名,
COUNT(DISTINCT MONTH(売上日)) AS 販売月数,
SUM(売上金額) AS 年間売上
FROM sales
WHERE YEAR(売上日) = 2024
GROUP BY 商品名
HAVING COUNT(DISTINCT MONTH(売上日)) >= 9; -- 9ヶ月以上販売
月別売上の安定性をチェック
SELECT 商品名,
COUNT(*) AS 販売回数,
AVG(売上金額) AS 平均売上,
STDDEV(売上金額) AS 売上標準偏差
FROM sales
GROUP BY 商品名
HAVING COUNT(*) >= 30 -- 十分なサンプル数
AND STDDEV(売上金額) / AVG(売上金額) < 0.5; -- 変動係数50%未満
HAVINGとサブクエリの組み合わせ
全体平均を超えるグループを抽出
SELECT 地域,
AVG(売上金額) AS 地域平均売上
FROM sales
GROUP BY 地域
HAVING AVG(売上金額) > (
SELECT AVG(売上金額) FROM sales
);
上位パーセンテージのグループを抽出
SELECT 商品名, SUM(売上金額) AS 合計売上
FROM sales
GROUP BY 商品名
HAVING SUM(売上金額) >= (
SELECT PERCENTILE_CONT(0.8) WITHIN GROUP (ORDER BY total_sales)
FROM (
SELECT SUM(売上金額) as total_sales
FROM sales
GROUP BY 商品名
) as product_sales
);
実務でよく使うHAVINGパターン
ABC分析での活用
SELECT 商品名,
SUM(売上金額) AS 合計売上,
SUM(売上金額) / (SELECT SUM(売上金額) FROM sales) * 100 AS 売上構成比
FROM sales
GROUP BY 商品名
HAVING SUM(売上金額) / (SELECT SUM(売上金額) FROM sales) >= 0.01 -- 1%以上
ORDER BY 合計売上 DESC;
異常値の検出
SELECT 顧客ID,
COUNT(*) AS 購入回数,
AVG(購入金額) AS 平均購入金額
FROM orders
GROUP BY 顧客ID
HAVING COUNT(*) > 100 -- 異常に多い購入回数
OR AVG(購入金額) > 500000; -- 異常に高い平均購入金額
HAVINGを使いこなすことで、より精密な分析が可能になります。最後に、これまでの内容をまとめて実用的な活用方法を整理しましょう。
まとめ:GROUP BY複数条件を実務で活用しよう

この記事では、SQLのGROUP BYで複数条件を指定する方法について詳しく解説しました。
重要なポイント
基本的な仕組みの理解
- GROUP BYは複数列を指定して詳細な集計が可能
- 指定した列の組み合わせごとにグループが作られる
- SELECT句には集計関数またはGROUP BY句の列のみ指定可能
各句の役割と使い分け
句 | 用途 | 適用タイミング | 例 |
---|---|---|---|
WHERE | 行レベルの絞り込み | GROUP BY前 | 地域=’東京’ |
GROUP BY | 集計単位の指定 | – | 年, 商品名 |
HAVING | グループレベルの絞り込み | GROUP BY後 | SUM(売上) > 100000 |
ORDER BY | 表示順の制御 | 最後 | 年 ASC, 売上 DESC |
実務でよく使うパターン
売上分析
-- 月別・商品別・地域別の売上分析
SELECT YEAR(売上日) AS 年,
MONTH(売上日) AS 月,
地域,
商品カテゴリ,
SUM(売上金額) AS 売上合計,
COUNT(*) AS 販売件数,
AVG(売上金額) AS 平均単価
FROM sales
WHERE 売上日 >= '2024-01-01'
GROUP BY YEAR(売上日), MONTH(売上日), 地域, 商品カテゴリ
HAVING SUM(売上金額) > 50000
ORDER BY 年, 月, 地域, 売上合計 DESC;
顧客分析
-- 顧客セグメント別の購買行動分析
SELECT 年齢層,
性別,
会員ランク,
COUNT(DISTINCT 顧客ID) AS 顧客数,
COUNT(*) AS 購入回数,
SUM(購入金額) AS 総購入金額,
AVG(購入金額) AS 平均購入金額
FROM orders o
JOIN customers c ON o.顧客ID = c.顧客ID
WHERE 購入日 >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
GROUP BY 年齢層, 性別, 会員ランク
HAVING COUNT(DISTINCT 顧客ID) >= 10
ORDER BY 総購入金額 DESC;
在庫分析
-- 商品別・倉庫別の在庫回転率分析
SELECT 商品カテゴリ,
倉庫,
SUM(出庫数量) AS 総出庫数,
AVG(在庫数量) AS 平均在庫数,
SUM(出庫数量) / AVG(在庫数量) AS 在庫回転率
FROM inventory_movements
WHERE 日付 >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY 商品カテゴリ, 倉庫
HAVING AVG(在庫数量) > 0
ORDER BY 在庫回転率 DESC;
パフォーマンス最適化のコツ
効率的なクエリの書き方
- WHERE句で事前にデータを絞り込む
- インデックスが効く列でGROUP BYを行う
- 不要な列は SELECT しない
- 適切な集計関数を選択する
インデックス設計の考慮
-- 複合インデックスの例
CREATE INDEX idx_sales_analysis
ON sales (売上日, 地域, 商品カテゴリ);
-- GROUP BY でよく使う列の組み合わせにインデックスを作成
エラー回避のチェックポイント
よくあるエラーと対策
- 集計していない列をSELECTに含める:GROUP BYに追加するか集計関数を使用
- HAVINGとWHEREの混同:適用タイミングを理解する
- NULL値の扱い:IS NULLやCOALESCE関数で適切に処理
- データ型の不一致:CAST関数で型変換を行う
コメント