0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQL 集計で迷わない!GROUP BY / OVER の使い分け入門

Last updated at Posted at 2025-12-23

はじめに

SQL の集計でよく出てくる

  • GROUP BY
  • OVER (PARTITION BY ...)(ウィンドウ関数)

ですが、

  • なんとなく使っている
  • 動くから OK にしている

というケースも多いのではないでしょうか。

本記事では

「どちらを使うべきか」

ではなく

「どこで選択を誤ると設計が壊れるか」

という視点で整理します。


OVER(ウィンドウ関数)とは?

ウィンドウ関数は、
1行ずつのデータを保持したまま集計や順位、累計などの情報を追加できる
SQL 構文です。

GROUP BY のように行をまとめるのではなく、
行を消さずに「文脈」を付与するイメージです。

入力テーブル:employees

employee_id department_id salary
1 A 3000
2 A 4000
3 B 3500
4 B 4500

SQLクエリ例

SELECT
  employee_id,
  department_id,
  salary,
  AVG(salary) OVER (PARTITION BY department_id) AS dept_avg
FROM employees;

出力テーブル

employee_id department_id salary dept_avg
1 A 3000 3500
2 A 4000 3500
3 B 3500 4000
4 B 4500 4000
  • 各社員の行はそのまま残る
  • 同じ部門の平均給与を dept_avg 列として追加

ポイント:

  • 行の責務を保持したまま集計情報を付与できる
  • 順位付け(RANK)、累計(SUM OVER)、平均との差(AVG OVER など)も可能

注意点

  • WHERE句では直接使えない(サブクエリやCTEが必要)
  • 大量データや複雑なパーティションでは計算コストが増える可能性

GROUP BY のアンチパターン

アンチパターン①:行を残したいのに GROUP BY する

やりがちな場面

  • 社員ごとの売上や給与レポートを作っているつもりで、部門単位でまとめてしまう
  • 後から「社員ごとの情報も必要」となり、再JOINなどで対応しなければならない

入力テーブル:employees

employee_id department_id salary
1 A 3000
2 A 4000
3 B 3500
4 B 4500

SQLクエリ

SELECT
  employee_id,
  department_id,
  AVG(salary)
FROM employees
GROUP BY department_id;

返却されるテーブル

employee_id department_id avg_salary
? A 3500
? B 4000

問題点

  • employee_id が意味を失っている
  • 社員単位の情報が消える
  • 「行の責務」が壊れている

兆候

  • 後続で「社員ごとの〇〇」を扱いたくなる
  • 再 JOIN が発生する

👉 元の行に意味があるなら OVER を検討すべき


アンチパターン②:GROUP BY → 再 JOIN 前提の設計

やりがちな場面

  • 月次レポートで部門ごとの平均給与を出すつもりでGROUP BY
  • 後で個別社員ランキングや累計給与も必要になり、クエリが冗長化

入力テーブル:employees

employee_id department_id salary
1 A 3000
2 A 4000
3 B 3500
4 B 4500

SQLクエリ

WITH dept_avg AS (
  SELECT department_id, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY department_id
)
SELECT e.*, d.avg_salary
FROM employees e
JOIN dept_avg d
  ON e.department_id = d.department_id;

返却されるテーブル

employee_id department_id salary avg_salary
1 A 3000 3500
2 A 4000 3500
3 B 3500 4000
4 B 4500 4000

問題点

  • クエリが冗長
  • 中間集計の責務が不明瞭
  • 「集計を付与したいだけ」なのに行を消している

👉 最初から OVER で書けば 1 クエリで完結


アンチパターン③:将来拡張を考えず GROUP BY 固定

やりがちな場面

  • 月次レポートで部門ごとの平均給与だけを計算するつもりでGROUP BYを使用
  • 後から「個別社員のランキング」「平均との差」「累計給与」などが必要になったとき、既存クエリを全面的に書き換える必要が出る

入力テーブル:employees

employee_id department_id salary
1 A 3000
2 A 4000
3 B 3500
4 B 4500

最初に書いたGROUP BYクエリ(平均だけ)

SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

返却されるテーブル

department_id avg_salary
A 3500
B 4000

後からランキングを追加したい場合

WITH dept_avg AS (
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
)
SELECT e.employee_id, e.department_id, e.salary,
       RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank
FROM employees e
JOIN dept_avg d
  ON e.department_id = d.department_id;

返却されるテーブル

employee_id department_id salary salary_rank
2 A 4000 1
1 A 3000 2
4 B 4500 1
3 B 3500 2
  • 今は平均だけ
  • 次は順位
  • その次は平均との差・累計

この状態で GROUP BY を選ぶと
後から全面書き換えになりがち。

👉 行に文脈を付けたいなら OVER の方が拡張しやすい


OVER(ウィンドウ関数)のアンチパターン

アンチパターン①:集計表なのに OVER + DISTINCT

やりがちな場面

  • 社員ごとの給与レポートを作るつもりが、部門平均だけを計算してしまう
  • 後で社員ごとの情報も必要になり、再JOINやDISTINCTで無理やり行を減らす
SELECT DISTINCT
  department_id,
  AVG(salary) OVER (PARTITION BY department_id)
FROM employees;

問題点

  • 意味的には集計表
  • DISTINCT で無理やり行を減らしている
  • 可読性が低い

👉 集計結果だけなら素直に GROUP BY


アンチパターン②:WHERE で使えると思っている

やりがちな場面

  • 部門ごとの平均給与を算出して社員レコードに付与したい
  • 最初は平均だけで良いと思ったが、後で「平均を超えた社員だけ抽出」などの条件が必要になり、書けなくて戸惑う
SELECT *
FROM employees
WHERE salary > AVG(salary) OVER (PARTITION BY department_id);

❌ これは書けない

理由

  • WHERE は SELECT より前
  • ウィンドウ関数は SELECT フェーズ

👉 サブクエリや CTE が必要
👉 理解不足のサインになりやすい


アンチパターン③:行が不要なのに OVER を多用

やりがちな場面

  • 月次レポートや集計表で「部門ごとの合計や平均だけが必要」なのに、社員ごとの行に対して複数のウィンドウ関数を計算してしまう
  • 後で最終結果が1行だけで十分なのに、余計な計算コストが発生していることに気づ
AVG(salary) OVER (PARTITION BY department_id)
MAX(salary) OVER (PARTITION BY department_id)
MIN(salary) OVER (PARTITION BY department_id)

問題点

  • 計算コストが増える
  • 意図が読み取りづらい

👉 最終結果が1行なら GROUP BY


設計判断のアクティビティ図(思考フロー)

開始
 ↓
「最終成果物は “集計された行” か?」
 ├─ 集計された行
 │     ↓
 │   GROUP BY
 │
 └─ 元データの行を保持したい
        ↓
「1行1行に業務的な意味があるか?」
 ├─ ある(社員・注文・ログなど)
 │     ↓
 │   OVER(ウィンドウ関数)
 │
 └─ ない(ただの中間データ)
        ↓
「ランキング・累計・平均との差など
 順序や比較が関係するか?」
 ├─ する
 │     ↓
 │   OVER
 │
 └─ しない
        ↓
「集計結果だけを使うか?」
 ├─ はい → GROUP BY
 └─ いいえ → OVER

アンチパターンから分かる対応関係

状態 見直すべき選択
GROUP BY → 再 JOIN OVER
DISTINCT + OVER GROUP BY
行単位比較で GROUP BY OVER
集計表なのに OVER GROUP BY

レビューで使える一言フレーズ

  • 行を消すなら GROUP BY
  • 行を残すなら OVER
  • JOIN で戻すなら最初から OVER
  • DISTINCT が出てきたら設計を疑う

まとめ

GROUP BY
→ 結果を作るための構文(行を減らす)

OVER(ウィンドウ関数)
→ 文脈を付与するための構文(行を残す)

どちらが高機能なのではなく、

行の責務をどう扱いたいかで選ぶのが

設計として自然です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?