GROUP BY は同じ値を持つ行をグループにまとめます。これにより、COUNT、SUM、AVG などの集計関数はテーブル全体で1つではなく、グループごとに1つの結果を返します。例は PostgreSQL、MySQL、SQLite、SQL Server で動作します。
GROUP BY の使い方
GROUP BY の後にグループ化する列を指定し、SELECT する他の列はすべて集計関数(COUNT、SUM、AVG、MIN、MAX)で囲みます。異なる値ごとに1行ずつ返されます:
SELECT group_column, AGGREGATE(value_column)
FROM table_name
GROUP BY group_column;
-- Input: scores
-- player | team | points
-- -------+------+-------
-- Ana | Red | 12
-- Ben | Blue | 9
-- Cleo | Red | 7
-- Dev | Blue | 15
-- Eli | Red | 4
SELECT team, SUM(points) AS total_points, COUNT(*) AS players
FROM scores
GROUP BY team;
-- Output:
-- team | total_points | players
-- -----+--------------+--------
-- Red | 23 | 3
-- Blue | 24 | 2
5行がチームごとに1行ずつ、計2行になり、各集計はそのチームの行だけを対象にします。
知っておくべきルール
- SELECT する列はすべて GROUP BY に含めるか、集計関数の中に入れる必要があります。そうしないとグループに表示する値が1つに決まらず、クエリはエラーになります:
-- Input: products
-- name | category
-- --------+---------
-- Blender | Kitchen
-- Toaster | Kitchen
-- Pillow | Bedroom
SELECT name, category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- ERROR: column "products.name" must appear in the GROUP BY clause
-- or be used in an aggregate function
-- (SQLite runs it anyway and takes name from one row in each group)
列を GROUP BY に追加するか、たとえば MIN(name) のようにその列も集計することで修正できます。
- NULL は1つのグループになります。グループ化する列が NULL の行はすべて同じグループに入ります。COALESCE を使ってラベルを付けましょう:
-- Input: products
-- name | category
-- ------------+-----------
-- Notebook | Stationery
-- Gift Card | NULL
-- Pen Set | Stationery
-- Mystery Box | NULL
-- Stapler | Stationery
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- Output:
-- category | product_count
-- -----------+--------------
-- NULL | 2
-- Stationery | 3
SELECT COALESCE(category, 'Uncategorized') AS category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- Output:
-- category | product_count
-- --------------+--------------
-- Uncategorized | 2
-- Stationery | 3
-
WHERE はグループ化の前に行を絞り込み、HAVING はグループ化の後にグループを絞り込みます。
COUNT(*) > 2のような集計を条件にできるのは HAVING だけです。集計が不要な条件は WHERE に書けば、グループ化する行が少なくなります。 - グループの並び順は決まっていません。たとえば上記のスコアのクエリでは、SQLite と SQL Server は Red より先に Blue を返します。順序が重要な場合は必ず ORDER BY を追加してください。
別の書き方
式を繰り返す代わりに、列の別名や SELECT リスト内の列の位置でグループ化することもできます:
-- Input: orders
-- order_id | amount
-- ---------+-------
-- 1 | 35.00
-- 2 | 240.00
-- 3 | 18.50
-- 4 | 120.00
-- 5 | 64.00
SELECT CASE WHEN amount >= 100 THEN 'large' ELSE 'small' END AS order_size,
COUNT(*) AS order_count
FROM orders
GROUP BY order_size -- or GROUP BY 1
ORDER BY order_size;
-- SQL Server allows neither: repeat the CASE expression in GROUP BY
-- Output:
-- order_size | order_count
-- -----------+------------
-- large | 2
-- small | 3
ROLLUP は小計と総計を追加します。小計の行では、合計対象の列が NULL で表示されます:
-- Input: sales
-- region | category | amount
-- -------+----------+-------
-- East | Books | 100.00
-- East | Toys | 50.00
-- West | Books | 80.00
-- East | Books | 40.00
SELECT region, category, SUM(amount) AS total_sales
FROM sales
GROUP BY ROLLUP(region, category);
-- MySQL: GROUP BY region, category WITH ROLLUP
-- SQLite has no ROLLUP
-- Output:
-- region | category | total_sales
-- -------+----------+------------
-- East | Books | 140.00
-- East | Toys | 50.00
-- East | NULL | 190.00
-- West | Books | 80.00
-- West | NULL | 80.00
-- NULL | NULL | 270.00
集計関数を使わない GROUP BY は、異なる値をそれぞれ1回ずつ返します。これは SELECT DISTINCT category FROM products と同じです。
GROUP BY はいつ使うべき?
「〜ごと」や「〜それぞれ」を問う場面では GROUP BY を使います。顧客ごとの注文数、月ごとの売上、部署ごとの従業員数などです。ほとんどの用途は次の4つのいずれかに当てはまります。
グループごとに行数を数える
ステータスごとの注文数、カテゴリごとの商品数、日ごとの登録者数などです。COUNT(*) はグループ内のすべての行を数え、COUNT(column) はその列が NULL でない行だけを数えます。
グループごとの合計、平均、範囲
SUM、AVG、MIN、MAX は各グループの値を1つの数値にまとめます。顧客ごとの売上、平均注文額、カテゴリ内で最も安い商品と最も高い商品などです。1つのクエリで必要なだけ返せます。
複数の列で結果を分類する
GROUP BY の後に複数の列を並べると、組み合わせごとに1行が返されます。地域別・月別の売上、倉庫別・商品タイプ別の在庫などです。データに存在する組み合わせだけが表示されます。
必要なグループだけを残す
HAVING はグループが作られた後、その集計値を使ってグループを絞り込みます。10人を超える部署、$1,000 以上使った顧客、または2回以上出現する値などです。最後の例は重複を見つける方法そのものです。
GROUP BY の実例
空のカテゴリも含めて、全カテゴリの商品数を数える
GROUP BY は存在する値に対してのみグループを作ります。商品のないカテゴリも一覧に含めるには、categories テーブルから始めて products を LEFT JOIN します。* ではなく結合したテーブルの列を数えてください。空のカテゴリにも NULL で埋められた1行が残り、COUNT(*) はそれを数えてしまいます。
-- Input: categories
-- category_name
-- -------------
-- Audio
-- Cameras
-- Drones
-- Input: products
-- id | name | category
-- ---+------------+---------
-- 1 | Earbuds | Audio
-- 2 | Soundbar | Audio
-- 3 | Action Cam | Cameras
-- Wrong: COUNT(*) counts the NULL row of an empty category
SELECT c.category_name, COUNT(*) AS product_count
FROM categories c
LEFT JOIN products p ON p.category = c.category_name
GROUP BY c.category_name
ORDER BY c.category_name;
-- Output:
-- category_name | product_count
-- --------------+--------------
-- Audio | 2
-- Cameras | 1
-- Drones | 1
-- Right: COUNT(p.id) skips it
SELECT c.category_name, COUNT(p.id) AS product_count
FROM categories c
LEFT JOIN products p ON p.category = c.category_name
GROUP BY c.category_name
ORDER BY c.category_name;
-- Output:
-- category_name | product_count
-- --------------+--------------
-- Audio | 2
-- Cameras | 1
-- Drones | 0
売上上位3カテゴリを求める
カテゴリごとの売上を合計し、合計で並べ替えて上位3件を残します。Books は個々の売上が Toys の1件の売上より小さいにもかかわらず、2位になります:
-- Input: purchases
-- category | amount
-- ---------+-------
-- Books | 45.00
-- Toys | 120.00
-- Garden | 310.00
-- Books | 80.00
-- Music | 25.00
SELECT category, SUM(amount) AS revenue
FROM purchases
GROUP BY category
ORDER BY revenue DESC
LIMIT 3;
-- SQL Server has no LIMIT: write SELECT TOP 3 category, ... instead
-- Output:
-- category | revenue
-- ---------+--------
-- Garden | 310.00
-- Books | 125.00
-- Toys | 120.00
地域別・月別の売上をレポートする
地域と、日付から取り出した月でグループ化すると、各地域に月ごとに1行ずつ返されます:
-- Input: sales
-- region | sale_date | amount
-- -------+------------+-------
-- East | 2024-01-08 | 120.00
-- West | 2024-01-15 | 80.00
-- East | 2024-01-22 | 95.00
-- East | 2024-02-03 | 60.00
-- West | 2024-02-11 | 150.00
SELECT region, EXTRACT(MONTH FROM sale_date) AS month, SUM(amount) AS total_sales
FROM sales
GROUP BY region, EXTRACT(MONTH FROM sale_date)
ORDER BY region, month;
-- Output:
-- region | month | total_sales
-- -------+-------+------------
-- East | 1 | 215.00
-- East | 2 | 60.00
-- West | 1 | 80.00
-- West | 2 | 150.00
SQL Server と SQLite には EXTRACT がありません。SQL Server では MONTH(sale_date)、SQLite では strftime('%m', sale_date) を使ってください。データが複数年にまたがる場合は、年でもグループ化しましょう。
今年のリピート顧客を見つける
WHERE で2024年の注文だけを残し、HAVING でそのうち2件以上ある顧客を残します。顧客 202 は合計2件の注文がありますが、2024年は1件だけなので除外されます:
-- Input: orders
-- order_id | customer_id | order_date
-- ---------+-------------+-----------
-- 1 | 201 | 2023-11-20
-- 2 | 202 | 2023-12-05
-- 3 | 201 | 2024-01-09
-- 4 | 202 | 2024-02-14
-- 5 | 201 | 2024-03-02
SELECT customer_id, COUNT(*) AS orders_2024
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING COUNT(*) >= 2;
-- Output:
-- customer_id | orders_2024
-- ------------+------------
-- 201 | 2
GROUP BY についてさらに学ぶ
このブログの関連記事:
- SQL COUNT DISTINCT ガイド: グループごとにユニークな値を数える
-
SQL で重複を見つけるガイド: GROUP BY と
HAVING COUNT(*) > 1 - SQL ORDER BY ガイド: グループ化した結果を並べ替える
- SQL CASE 文ガイド: 集計関数内での条件付きカウントと合計
- SQL ピボットテーブルガイド: グループ化した行を列に変換する
- SQL COALESCE ガイド: NULL グループにラベルを付ける
- SQL JOIN 図解ガイド: 空のグループを残す LEFT JOIN
公式ドキュメント:
Beekeeper Studioは無料のオープンソースのデータベースGUIです
これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。
Beekeeper Studio 利用者の声
「Beekeeper Studio は以前の SQL ワークフローを完全に置き換えました。高速で直感的で、データベース作業がまた楽しくなりました。」
「多くのデータベース GUI を試してきましたが、Beekeeper は機能とシンプルさのバランスが完璧です。とにかくちゃんと動きます。」