SQLのGROUP BYで複数条件を指定する方法|使い方・注意点・実用例まで解説!

データベース・SQL

「売上を月ごと・商品ごとに集計したい」
「ユーザーの年齢と性別ごとに平均値を出したい」
そんなときに役立つのが、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 年 の結果

件数
20232
20242

よく使う集計関数

基本的な集計関数

  • 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;

この例では:

  1. 年と商品名でグループ化して集計
  2. 年の昇順、売上合計の降順で並び替え

実用的な並び替え例

売上上位の商品を表示

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の実行順序

  1. FROM:テーブルを指定
  2. WHERE:行レベルでフィルタリング
  3. GROUP BY:グループ化
  4. HAVING:グループレベルでフィルタリング
  5. SELECT:列を選択
  6. 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関数で型変換を行う

コメント

タイトルとURLをコピーしました