SQLite | ゼロからはじめるSQL、30日で習得するSQLite:検索力強化 - Day13 条件付き集計

SQL SQLite
スポンサーリンク

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 円以上のユーザーだけを知りたい」

日本語で分解すると、

  1. user_id ごとに SUM(amount) を計算する
  2. その合計が 5000 以上のグループだけを残す

という二段階です。

1 は GROUP BY user_idSUM(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

流れを言葉で追うと、こうなります。

  1. orders の行を user_id ごとにグループに分ける
  2. 各グループについて SUM(amount) を計算する
  3. その中から「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 円以上のユーザーだけを知りたい」

これを分解すると、

  1. 2025-04-01 の注文だけに絞る(行レベルの条件)
  2. user_id ごとにグループ化して SUM(amount) を計算する
  3. 合計が 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 でさらに絞る」実務的なパターン
を扱って、条件付き集計の感覚をさらに固めていきます。

タイトルとURLをコピーしました