【SQL】GROUP BYでグループごとに集計する|SQLの基本を初心者向けに解説

SQL

こんにちは、かつコーチです。

「COUNT・SUM・AVGは使えるようになったけど、『顧客ごとの注文件数』のように、グループ単位で集計したいときはどう書けばいいんだろう」と悩んでいませんか。

この記事では、そんな悩みを解決するGROUP BYの使い方を、実際に手を動かしながら理解できるように解説します。

読み終わる頃には、「◯◯ごとに集計する」というよくあるニーズをSQL一文で解決できるようになります。

GROUP BYとは?

グループごとに集計するための句

GROUP BYとは、指定した列の値が同じ行を一つのグループにまとめ、そのグループ単位で集計関数(COUNT・SUM・AVG・MAX・MINなど、複数行の値をまとめて一つの値にする関数)を適用するための句です。

以前の記事「集計関数(COUNT・SUM・AVG・MAX・MIN)の使い方」では、テーブル全体を一つの塊として集計する方法を紹介しました。

GROUP BYを使うと、テーブル全体ではなく「顧客ごと」「日付ごと」「商品ごと」といった単位で集計できるようになります。

なぜGROUP BYが必要なのか

たとえば「顧客ごとの注文件数を知りたい」という要望は、実務でとてもよく出てきます。

GROUP BYを知らないと、顧客ごとにSELECT文を何回も実行して手動で集計する、といった非効率な方法に頼ってしまいがちです。

GROUP BYを使えば、この作業をSQL一文で完結させられます。

基本の書き方

この記事では、以下のサンプルテーブルを使います。

CREATE TABLE customers (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    prefecture VARCHAR(20) NOT NULL
);

CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date DATE NOT NULL,
    total_amount INT NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

INSERT INTO customers (name, prefecture) VALUES
('田中', '東京都'),
('佐藤', '大阪府'),
('鈴木', '東京都');

INSERT INTO orders (customer_id, order_date, total_amount) VALUES
(1, '2026-08-01', 3000),
(1, '2026-08-10', 5000),
(2, '2026-08-03', 2000),
(3, '2026-08-05', 7000);

手順1:単一の列でグループ化する

顧客ごとの注文件数を数えるには、次のように書きます。

SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

実行結果は以下のようになります。

customer_idorder_count
12
21
31

customer_idが同じ行を1グループにまとめ、そのグループごとにCOUNT(*)を計算しています。

手順2:SUM・AVGと組み合わせる

合計金額や平均金額を出したいときも同じ考え方です。

SELECT customer_id, SUM(total_amount) AS total_spent, AVG(total_amount) AS avg_amount
FROM orders
GROUP BY customer_id;

顧客ごとの合計購入額・平均購入額が一気に取得できます。

手順3:複数の列でグループ化する

GROUP BYには複数の列を指定することもできます。

SELECT customer_id, order_date, SUM(total_amount) AS daily_total
FROM orders
GROUP BY customer_id, order_date;

この場合は「顧客IDと日付の組み合わせ」が同じ行を1グループとして扱います。

「同じ顧客が同じ日に複数回注文した場合の合計」を出したいときに便利です。

つまずきやすい設定・注意点

SELECT句に書ける列の制限

GROUP BYを使うときに最初につまずきやすいのが、SELECT句に書ける列の制限です。

❌ Before:GROUP BYで指定していない列を、集計関数を通さずにそのままSELECT句に書いてしまう

SELECT customer_id, name, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

このクエリはMySQLの設定によってはエラーにならず動いてしまうことがありますが、name列がどの行の値を表しているのか論理的に不定になり、意図しない結果を招きます。

✅ After:GROUP BYで指定した列か、集計関数を通した列だけをSELECT句に書く

SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

私が実際にこのミスをしたときは、開発環境の設定(sql_modeONLY_FULL_GROUP_BYが含まれていない状態)ではエラーにならず、本番環境に近い設定のサーバーに移した途端にERROR 1055が出て焦った経験があります。

「エラーが出ないから正しい」とは限らないので、GROUP BYを使うときは「SELECT句にはGROUP BYの列か集計関数だけ」というルールを徹底しておくと安全です。

GROUP BYとWHEREの実行順序

もう一つ注意したいのが、WHERE句とGROUP BYの実行順序です。

SQLは書いた順番ではなく、FROM → WHERE → GROUP BY → SELECTという決まった順序で実行されます。

つまり、WHERE句はグループ化される「前」の行を絞り込む条件であり、グループ化した「後」の結果を絞り込むことはできません。

SELECT customer_id, COUNT(*) AS order_count
FROM orders
WHERE order_date >= '2026-08-01'
GROUP BY customer_id;

このクエリは「2026年8月1日以降の注文だけを対象に」グループ化しています。

グループ化した後の結果(たとえば「注文件数が2件以上の顧客だけ」)を絞り込みたい場合は、次回解説するHAVING句を使う必要があります。

応用・一歩先の使い方

ORDER BYと組み合わせて集計結果を並び替える

GROUP BYの結果は、ORDER BYと組み合わせることでランキング的な使い方もできます。

SELECT customer_id, SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
ORDER BY total_spent DESC;

「購入金額が多い顧客順」に並び替えられるので、優良顧客の分析などにそのまま使えます。

GROUP BYと集計結果の絞り込みへ

GROUP BYで集計した結果を「条件で絞り込みたい」というニーズは非常に多く出てきます。

その際に使うのがHAVING句です。

次回の記事「HAVINGでグループ集計結果を絞り込む」で、WHEREとの違いを含めて詳しく解説します。

まとめ

この記事のポイント

  • GROUP BYは指定した列の値が同じ行をグループ化し、集計関数を適用するための句
  • SELECT句にはGROUP BYで指定した列か、集計関数を通した列だけを書く
  • SQLはFROM → WHERE → GROUP BY → SELECTの順に実行されるため、WHEREはグループ化前の絞り込みにしか使えない
  • 複数列を指定すれば、組み合わせ単位でのグループ化も可能

次に読むべき記事

グループ化した結果をさらに条件で絞り込みたい方は、次回の「HAVINGでグループ集計結果を絞り込む」に進んでください。


タグ: SQL, 初心者向け, 集計

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