SQL:GROUP BY
1学習の目的
- GROUP BY でカテゴリ別・月別といったグループごとの集計ができるようになる。前章では全体で1行だった結果が、切り口ごとの一覧になる。
- SELECT に書ける列のルールと、NULLがどうグループ化されるかを理解する。Excelのピボットテーブルに相当する処理を、SQLで自在に組めるようになる。
2基礎解説
前章の集計はテーブル全体で1行でした。GROUP BY を付けると、グループごとに1行ずつ集計されます。
| やりたいこと | 書き方 | 結果 |
|---|---|---|
| 全体を集計 | SELECT COUNT(*) FROM products | 1行 |
| グループ別に集計 | GROUP BY category | カテゴリの数だけ行 |
| 並べ替える | GROUP BY … ORDER BY 合計 DESC | 集計値で並べ替え |
| 絞ってから集計 | WHERE … GROUP BY … | WHERE が先 |
- SELECT に書けるのは「グループ化した列」と「集計関数」だけ。GROUP BY category のときに SELECT name と書くと、1グループに複数の名前があるため意味をなさない。
- NULLは1つのグループにまとまる。「値が入っていない」という共通点でグループ化され、独立した行として現れる。
- 句の順番は SELECT → FROM → WHERE → GROUP BY → ORDER BY → LIMIT。WHERE はグループ化の前に効く(行を減らしてから集計する)。
- ORDER BY には集計結果の別名も使える。ORDER BY 合計 DESC で「売上の多いカテゴリ順」が作れる。
- グループ化の基準は複数指定できるし、計算式や CASE 式も使える。切り口を自由に設計できるのが GROUP BY の強み。
現場使用例:カテゴリ別売上、月別の注文件数、担当者ごとの成約数、地域別の会員数。Excelのピボットテーブルに相当する処理で、レポート作成の中心になる。
-- カテゴリごとの件数 SELECT category, COUNT(*) AS 件数 FROM products GROUP BY category; -- → NULL / PC / 周辺機器 の3行 -- 複数の集計をまとめて SELECT category, COUNT(*) AS 件数, SUM(price) AS 合計, AVG(price) AS 平均 FROM products GROUP BY category; -- 集計値で並べ替える SELECT category, SUM(price) AS 合計 FROM products GROUP BY category ORDER BY 合計 DESC; -- 絞ってから集計する(WHERE が先) SELECT category, COUNT(*) AS 件数 FROM products WHERE price IS NOT NULL GROUP BY category; -- NULLに名前を付けてからグループ化 SELECT COALESCE(category, '未分類') AS 分類, COUNT(*) AS 件数 FROM products GROUP BY 分類;
3基本ドリル(10問)
3行。NULL 1 / PC 2 / 周辺機器 4
GROUP BY category を FROM の後ろに付ける。
SELECT category, COUNT(*) AS 件数 FROM products GROUP BY category;
グループの数だけ行が返る。前章の集計は全体で1行だったが、GROUP BY を付けると切り口ごとの一覧になる。
A. 1行 B. 3行 C. 7行 D. 2行 選択★☆☆無料
解答 == B
グループの数だけ返る。NULLも1つのグループ。
B
NULLも独立した1グループになる。「値が入っていない」という共通点でまとめられ、行として現れる。
3行。NULL 25000 / PC 173000 / 周辺機器 13200
COUNT(*) の代わりに SUM(price) を使う。
SELECT category, SUM(price) AS 合計 FROM products GROUP BY category;
グループごとに合計が計算される。周辺機器は4件あるが、priceがNULLの1件は合計から除かれている(SQL06)。
SELECT category, AVG(price) AS 平均 FROM products ____ category;
3行。NULL 25000.0 / PC 86500.0 / 周辺機器 4400.0
「〜でグループ化する」を意味する2語。
GROUP BY
GROUP BY はグループ化の基準を指定する。この列の値が同じ行どうしが、1つのグループにまとめられる。
A. name B. COUNT(*) C. price D. stock 選択★☆☆無料
解答 == B
1グループに複数の値があるものは書けない。
B
グループ化した列と集計関数だけが書ける。周辺機器グループには4つの名前があるので、name と書いてもどれを表示すべきか決まらない。
⚠ SQLiteだけは例外的にエラーにならず、グループ内のどれか1つが返る(どれが返るかは保証されない)。MySQL・PostgreSQLなど多くの環境ではエラーになるので、書かないのが正解。
4列3行。PC は 2 / 173000 / 86500.0
集計関数をカンマで並べる。GROUP BY は1回でよい。
SELECT category, COUNT(*) AS 件数, SUM(price) AS 合計, AVG(price) AS 平均 FROM products GROUP BY category;
1回のグループ化で複数の集計が取れる。件数・合計・平均を別々に問い合わせる必要はない。
SELECT category, SUM(price) AS 合計 FROM products GROUP BY category ____ 合計 DESC;
3行。PC 173000 が先頭
並べ替えを指示する2語。GROUP BY の後ろに書く。
ORDER BY
ORDER BY には集計結果の別名が使える。「売上の多い順」というレポートの基本形が、この1行で作れる。
A. GROUP BY → WHERE B. WHERE → GROUP BY C. 順番は自由 D. 両方は使えない 選択★★☆無料
解答 == B
行を減らしてから集計するのか、集計してから減らすのか。
B
WHERE は集計する前に効く。まず条件で行を絞り、残った行をグループ化する。集計した「後」に絞りたい場合は、次章の HAVING を使う。
3行。PC 955000 / 周辺機器 467400 / NULLは空
SUM(price * stock) と書く。集計関数の中に計算式を入れられる。
SELECT category, SUM(price * stock) AS 在庫額 FROM products GROUP BY category;
NULLグループの在庫額は空になる。新商品Aは stock が NULL なので計算結果もNULLになり、合計する対象が無くなるため(SQL05・SQL06)。
3行。PC 2 / 周辺機器 4 / 未分類 1
COALESCE で置き換えた結果に別名を付け、その別名で GROUP BY する。
SELECT COALESCE(category, '未分類') AS 分類, COUNT(*) AS 件数 FROM products GROUP BY 分類;
置き換えてからグループ化できる。レポートに「NULL」と表示するより「未分類」のほうが、読む人に意味が伝わる。
4実践シナリオ(5問)
3行。PC 2 173000 が先頭
COALESCE で置き換え、GROUP BY し、ORDER BY 合計 DESC で並べる。
SELECT COALESCE(category, '未分類') AS 分類, COUNT(*) AS 商品数, SUM(price) AS 合計 FROM products GROUP BY 分類 ORDER BY 合計 DESC;
実務のレポートで最も多い形。「切り口ごとに集計して、大きい順に並べる」——売上でも件数でもこの構造は変わらない。
3行。周辺機器は 3 / 4400.0
WHERE price IS NOT NULL で絞ってから GROUP BY する。ROUND で桁を揃える。
SELECT category, COUNT(*) AS 件数, ROUND(AVG(price), 1) AS 平均 FROM products WHERE price IS NOT NULL GROUP BY category;
WHERE で絞ると件数そのものが変わる。周辺機器は本来4件だが、価格未入力の1件を除いたので3件になっている。何を分母にするかを明示できるのがこの書き方の利点。
3列3行。周辺機器は 4 / 3
COUNT(*) と COUNT(price) を並べる。両者の差が欠損件数になる。
SELECT category, COUNT(*) AS 全件, COUNT(price) AS 価格あり FROM products GROUP BY category;
2つの数を並べることで欠損が見える。周辺機器だけ 4 と 3 で差があり、そこに未入力が1件あると一目で分かる(SQL06の設計思想)。
4列3行。PC が先頭(平均86500.0)
MIN・MAX・ROUND(AVG(...),1) を並べ、ORDER BY 平均 DESC。
SELECT category, MIN(price) AS 最安, MAX(price) AS 最高, ROUND(AVG(price), 1) AS 平均 FROM products GROUP BY category ORDER BY 平均 DESC;
グループごとの価格帯が一目で分かる。PCは45000〜128000円、周辺機器は1500〜8500円と、扱う価格帯がまったく違うことが数字で示せる。
3行。PC 2 955000 が先頭、NULLグループは 1 0
COALESCE を price と stock の両方に適用してから掛ける(SQL05のS04)。
SELECT category, COUNT(*) AS 商品数, SUM(COALESCE(price, 0) * COALESCE(stock, 0)) AS 在庫額 FROM products GROUP BY category ORDER BY 在庫額 DESC;
COALESCE を入れないとNULLグループの在庫額が空欄になる(D09の結果)。0として扱えば「在庫金額ゼロ」という意味のある数字になり、並べ替えにも正しく参加する。
5仕上げ課題
表示する列
① 分類(category、NULLは「未分類」)→ 別名「分類」
② 商品数 → 別名「商品数」
③ 価格が入力済みの件数 → 別名「価格登録済」
④ 平均単価(NULLを除く、小数第1位まで)→ 別名「平均単価」
⑤ 最高値 → 別名「最高値」
⑥ 在庫金額の合計(NULLは0として扱う)→ 別名「在庫額」
並び順:在庫額の大きい順
期待される結果(6列 × 3行)
分類 | 商品数 | 価格登録済 | 平均単価 | 最高値 | 在庫額
PC | 2 | 2 | 86500.0 | 128000 | 955000
周辺機器 | 4 | 3 | 4400.0 | 8500 | 467400
未分類 | 1 | 1 | 25000.0 | 25000 | 0
6列 × 3行が完全一致
COALESCE で分類名を作り、その別名で GROUP BY する。COUNT(*) と COUNT(price) を使い分け、在庫額は SUM(COALESCE(price,0) * COALESCE(stock,0))。並べ替えは ORDER BY 在庫額 DESC。
SELECT COALESCE(category, '未分類') AS 分類, COUNT(*) AS 商品数, COUNT(price) AS 価格登録済, ROUND(AVG(price), 1) AS 平均単価, MAX(price) AS 最高値, SUM(COALESCE(price, 0) * COALESCE(stock, 0)) AS 在庫額 FROM products GROUP BY 分類 ORDER BY 在庫額 DESC;
Excelのピボットテーブルと同じものが、SQL 1本で作れた。しかもこれは商品が100万件でも同じSQLで動く。
このレポートは読むだけで課題が見えるように設計されている。周辺機器は商品数4に対して価格登録済3——1件が未入力だ。未分類の在庫額が0なのは、在庫数そのものが未登録だから。数字を並べることで、データの不備が浮かび上がる。
そしてPCは商品数2なのに在庫額95万円で首位に立つ。商品数4の周辺機器(46万円)の2倍以上だ。「どのカテゴリに力を入れるか」を考えるとき、商品数だけ見ていては判断を誤る。切り口を変えると別の事実が見える——これがGROUP BYで分析する意味になる。
ただし今の状態には制限がある。すべてのグループが表示されてしまうことだ。「商品数が3件以上のカテゴリだけ見たい」と思っても、WHERE では書けない(WHEREは集計前に効くため)。次章の HAVING で、この問題を解決する。