TS-Tech
DB/SQL

GROUP BY と集計で合計・件数を出す

2
  • SQL

GROUP BY と集計で合計・件数を出す

行を眺めるだけでなく、「カテゴリ別の売上」「顧客ごとの注文数」を一発で出します。products · customers · orders を用意し、COUNT / SUM / AVGGROUP BY、あとで効いてくる HAVING まで進めます。


目次

  1. 準備
  2. 集計関数だけ使う
  3. GROUP BY
  4. JOIN してから集計
  5. HAVING
  6. よくあるつまずき

準備

cd $DB_SQL/PostgreSQL_setupdocker compose up -ddocker compose exec db psql -U practice -d practice_db
CREATE TABLE IF NOT EXISTS products (  id SERIAL PRIMARY KEY,  name TEXT NOT NULL,  category TEXT NOT NULL,  price INT NOT NULL);CREATE TABLE IF NOT EXISTS customers (  id SERIAL PRIMARY KEY,  name TEXT NOT NULL,  email TEXT NOT NULL UNIQUE);CREATE TABLE IF NOT EXISTS orders (  id SERIAL PRIMARY KEY,  customer_id INT NOT NULL REFERENCES customers(id),  product_id INT NOT NULL REFERENCES products(id),  qty INT NOT NULL CHECK (qty > 0),  ordered_at DATE NOT NULL DEFAULT CURRENT_DATE);TRUNCATE orders, customers, products RESTART IDENTITY;INSERT INTO products (name, category, price) VALUES  ('ノート A5', 'stationery', 300),  ('ボールペン', 'stationery', 120),  ('USB-C ケーブル', 'gadget', 980),  ('ワイヤレスマウス', 'gadget', 2500);INSERT INTO customers (name, email) VALUES  ('山田', 'yamada@example.com'),  ('佐藤', 'sato@example.com'),  ('鈴木', 'suzuki@example.com');INSERT INTO orders (customer_id, product_id, qty, ordered_at) VALUES  (1, 1, 2, '2026-08-01'),  (1, 3, 1, '2026-08-02'),  (2, 2, 5, '2026-08-03'),  (2, 4, 1, '2026-08-03');

集計関数だけ使う

表全体に対して:

SELECT COUNT(*) AS order_count FROM orders;SELECT SUM(qty) AS total_qty FROM orders;SELECT AVG(price)::NUMERIC(10, 2) AS avg_price FROM products;SELECT MIN(price) AS cheapest, MAX(price) AS priciest FROM products;

COUNT(*) は行数、COUNT(列) は NULL 以外の件数、という違いだけ覚えておくと後で楽です。


GROUP BY

カテゴリごとの商品数と平均価格:

SELECT  category,  COUNT(*) AS item_count,  ROUND(AVG(price), 1) AS avg_priceFROM productsGROUP BY categoryORDER BY category;

SELECT に書く非集計の列は、基本的に GROUP BY にも入れます。入れ忘れると PostgreSQL はエラーにします(これがむしろ親切です)。


JOIN してから集計

顧客ごとの注文件数と購入個数:

SELECT  c.name,  COUNT(o.id) AS orders,  COALESCE(SUM(o.qty), 0) AS total_qtyFROM customers cLEFT JOIN orders o ON o.customer_id = c.idGROUP BY c.id, c.nameORDER BY c.id;

鈴木さんは注文 0 件です。LEFT JOIN + COUNT(o.id) にしているので、顧客行は残しつつ件数が 0 になります。COUNT(*) にすると、注文が無くても 1 と数えやすいので注意です。LEFT JOIN 後の数え方は、一度やると忘れにくいです。

商品別の売上(qty × price):

SELECT  p.name,  SUM(o.qty) AS sold_qty,  SUM(o.qty * p.price) AS salesFROM orders oJOIN products p ON o.product_id = p.idGROUP BY p.id, p.nameORDER BY sales DESC;

HAVING

WHERE は集計前の行に効き、HAVING は集計後のグループに効きます。

売上が 1000 以上の商品だけ:

SELECT  p.name,  SUM(o.qty * p.price) AS salesFROM orders oJOIN products p ON o.product_id = p.idGROUP BY p.id, p.nameHAVING SUM(o.qty * p.price) >= 1000ORDER BY sales DESC;

「先に絞ってから集めたい」なら WHERE、「集めた結果で絞りたい」なら HAVING、と切り分けると迷いにくいです。


よくあるつまずき

症状 対処
must appear in the GROUP BY clause SELECT の素の列を GROUP BY に足す
鈴木が消える JOIN が INNER。顧客軸なら LEFT JOIN
HAVINGWHERE の違いが曖昧 集計前 / 後で使い分ける。同じ条件を両方に書かない
小数が長い ROUND::NUMERIC(10,2) で表示を整える

ワンライナー例:

docker compose exec db psql -U practice -d practice_db -c \  "SELECT category, COUNT(*) FROM products GROUP BY category;"

WHEREHAVING の前後関係が腹落ちすれば、集計の基本は押さえられています。

シェア