Day13 前半
「グループを作った“あと”に条件をかける」という新しい一手
Day12 で、GROUP BY を使って
「ユーザーごとの合計」
「日付ごとの件数」
のような グループ単位の集計 ができるようになりました。
Day13 のテーマは HAVING(ハビング)。
一言でいうと、
「グループ化した“あと”に、そのグループを条件でふるいにかける」ための句
です。
WHERE と似ているのに、役割が違う。
ここをきちんと分けて理解できると、一気に SQL が“プロっぽい顔”になります。
まずは復習:GROUP BY だけの世界
シンプルな orders テーブルをもう一度使います。
id | user_id | amount
---+---------+-------
1 | 1 | 1200
2 | 1 | 3000
3 | 2 | 500
4 | 3 | 8000
5 | 2 | 1500
Day12 でやった「ユーザーごとの合計購入金額」は、こうでした。
SELECT
user_id,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;
SQL結果はこうなります。
user_id | total_amount
--------+-------------
1 | 4200
2 | 2000
3 | 8000
ここまでは OK ですね。
ここからが本題:「合計がいくら以上のユーザーだけ」に絞りたい
次のような問いを考えます。
「合計購入金額が 5000 円以上のユーザーだけを知りたい」
日本語で分解すると、
- user_id ごとに SUM(amount) を計算する
- その合計が 5000 以上のグループだけを残す
という二段階です。
1 は GROUP BY user_id と SUM(amount) でできる。
問題は 2、「合計が 5000 以上」という条件をどこに書くか、です。
WHERE ではダメな理由
「WHERE は“グループ化前の行”にしか触れない」
多くの人が最初にやりがちなのが、こう書くパターンです。
SELECT
user_id,
SUM(amount) AS total_amount
FROM orders
WHERE SUM(amount) >= 5000 -- こう書きたくなる
GROUP BY user_id;
SQLでもこれはエラーになります。
理由はシンプルで、
WHERE は「1行1行」に対するフィルタSUM(amount) は「グループ全体」に対する集計結果
だからです。
グループがまだ作られていない段階(WHERE のタイミング)では、
「SUM(amount)」という値は存在しません。
つまり、
WHERE は「グループ化前の世界」にしか効かない
SUM / COUNT / AVG などは「グループ化後の世界」で意味を持つ
この時間差があるので、WHERE に集計結果を使うことはできません。
HAVING の役割
「グループ化した“あと”の世界に条件をかける」
そこで登場するのが HAVING です。
HAVING は、
「GROUP BY でグループを作った“あと”に、そのグループに対して条件をかける」
ための句です。
さっきの問い、
「合計購入金額が 5000 円以上のユーザーだけを知りたい」
は、こう書きます。
SELECT
user_id,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING SUM(amount) >= 5000;
SQL流れを言葉で追うと、こうなります。
- orders の行を user_id ごとにグループに分ける
- 各グループについて SUM(amount) を計算する
- その中から「SUM(amount) >= 5000」のグループだけを残す
結果はこうなります。
user_id | total_amount
--------+-------------
3 | 8000
user_id=1 → 4200(条件に届かない)
user_id=2 → 2000(条件に届かない)
user_id=3 → 8000(5000以上なので残る)
というふるい分けが行われたわけです。
WHERE と HAVING の役割分担を、はっきり言葉にする
ここで、一度整理しておきます。
WHERE
「グループ化する前の行」に対する条件
→ どの行を GROUP BY の対象にするかを決める
HAVING
「グループ化したあとのグループ」に対する条件
→ どのグループを最終結果として残すかを決める
この二つは、「どのタイミングの世界を見ているか」が違います。
行レベルでの条件 → WHERE
グループレベルでの条件 → HAVING
この切り分けが、Day13 の一番大事なポイントです。
WHERE と HAVING を両方使うパターンのイメージ
もう少しだけ踏み込んで、両方を使う場面をイメージしてみます。
問い:
「2025-04-01 の注文だけを対象にして、
その中で合計購入金額が 5000 円以上のユーザーだけを知りたい」
これを分解すると、
- 2025-04-01 の注文だけに絞る(行レベルの条件)
- user_id ごとにグループ化して SUM(amount) を計算する
- 合計が 5000 以上のグループだけを残す(グループレベルの条件)
SQL はこうなります。
SELECT
user_id,
SUM(amount) AS total_amount
FROM orders
WHERE date(order_date) = '2025-04-01'
GROUP BY user_id
HAVING SUM(amount) >= 5000;
SQLここでの流れは、
WHERE → GROUP BY → HAVING
という順番です。
「どの行を対象にするか」を WHERE で決めてから、
「どのグループを残すか」を HAVING で決める。
この二段階フィルタは、実務でもかなり頻繁に出てきます。
セキュリティの視点から見る HAVING の意味
「怪しいグループだけを抽出するフィルタ」
HAVING は、セキュリティ的にもかなり強力です。
たとえば、login_logs テーブルがあるとして、
「ログイン試行回数が 10 回以上のユーザーだけを一覧にしたい」
という問いを考えます。
これは、
user_id ごとに COUNT() で試行回数を数える
その中で、COUNT() >= 10 のユーザーだけを残す
という処理です。
SQL はこうなります。
SELECT
user_id,
COUNT(*) AS trial_count
FROM login_logs
GROUP BY user_id
HAVING COUNT(*) >= 10;
SQLこれで、「ログイン試行回数が多すぎる、怪しいユーザー」だけを抽出できます。
同じように、
IPアドレスごとのアクセス件数が一定以上のものだけ
日付ごとのエラー件数が異常に多い日だけ
といった「怪しいグループ」を HAVING でふるいにかけることができます。
Day13 前半の段階では、
「HAVING は、“グループごとの数字”に対して条件をかけるフィルタだ」
という感覚だけ持っておいてくれれば十分です。
Day13 前半のまとめ
GROUP BY は「グループを作る」、集計関数は「そのグループを数字にする」。
WHERE は「グループ化前の行」に対する条件で、どの行を対象にするかを決める。
HAVING は「グループ化後のグループ」に対する条件で、どのグループを残すかを決める。
「合計が〜以上のユーザーだけ」「回数が〜回以上のIPだけ」といった“条件付き集計”は HAVING の出番。
セキュリティの観点では、「怪しいグループ(試行回数が多いユーザーなど)」を抽出する強力なフィルタになる。
後半では、
HAVING と ORDER BY の組み合わせ、
複数の集計条件を HAVING に書く例、
「まず WHERE で絞ってから HAVING でさらに絞る」実務的なパターン
を扱って、条件付き集計の感覚をさらに固めていきます。
