Python | DB・SQL:HAVING

Python
スポンサーリンク

概要(HAVING は「集計結果に対する WHERE」だと思うと分かりやすい)

HAVING は、SQL の中でも「ちょっとだけ上級者向け」に見える句ですが、
本質はシンプルで、

「GROUP BY でグループ分けした“あと”の結果に対して条件をかける場所」

です。

WHERE は「グループを作る前の行」に対するフィルター。
HAVING は「グループを作った後の集計結果」に対するフィルター。

この違いさえつかめれば、HAVING は怖くありません。
ここから、具体例を使いながら、じっくりかみ砕いていきます。


まずは GROUP BY と集計の復習から

例として使うテーブルを決める

学習ログのテーブル study_logs を例にします。

study_logs テーブル

id | user_name | subject | minutes | date
---+-----------+---------+---------+------------
 1 | Taro      | math    | 60      | 2025-01-19
 2 | Taro      | english | 30      | 2025-01-19
 3 | Hanako    | math    | 45      | 2025-01-19
 4 | Taro      | math    | 40      | 2025-01-20
 5 | Hanako    | english | 50      | 2025-01-20

このテーブルから、「ユーザーごとの合計勉強時間」を出したいとします。
GROUP BY と SUM を使うと、こう書けます。

SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name;
SQL

結果は次のようになります。

user_name | total_minutes
----------+--------------
Taro      | 130   -- 60 + 30 + 40
Hanako    | 95    -- 45 + 50

ここまでは GROUP BY の基本です。
HAVING は、この「集計結果」に対してさらに条件をかけたいときに登場します。


HAVING の基本形と「WHERE との違い」

HAVING の基本構文

HAVING を使った SELECT の形は、だいたい次のようになります。

SELECT グループのキー, 集計関数(...)
FROM テーブル名
GROUP BY グループのキー
HAVING 集計結果に対する条件;
SQL

例えば、「ユーザーごとの合計勉強時間を出しつつ、合計が 100 分以上のユーザーだけに絞りたい」とします。
このときは、こう書きます。

SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name
HAVING SUM(minutes) >= 100;
SQL

結果は次の通りです。

user_name | total_minutes
----------+--------------
Taro      | 130

Hanako は 95 分なので、HAVING の条件に合わず除外されます。

ここで重要なのは、「HAVING の中で SUM(minutes) のような集計関数を使っている」という点です。
これが WHERE との一番大きな違いです。

WHERE ではダメなのか?をちゃんと理解する

さっきのクエリを、もし WHERE で書こうとすると、こうなります。

SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
WHERE SUM(minutes) >= 100      -- これはエラー
GROUP BY user_name;
SQL

多くのデータベースでは、これはエラーになります。
理由はシンプルで、「WHERE の時点ではまだ SUM(minutes) が計算されていないから」です。

SQL の処理の流れをざっくり書くと、

FROM でテーブルを読む
WHERE で行を絞る
GROUP BY でグループを作る
集計関数(SUM など)を計算する
HAVING でグループを絞る
SELECT で表示する列を決める

という順番になります。

つまり、SUM(minutes) が使えるのは「GROUP BY のあと」、
つまり HAVING や SELECT の中だけです。
WHERE は「グループを作る前の生の行」に対する条件なので、
集計関数は使えません。

この「処理の順番」を一度イメージしておくと、
HAVING の役割が一気にクリアになります。


WHERE と HAVING を組み合わせる実践パターン

まず WHERE で対象の行を絞り、そのあと HAVING で集計結果を絞る

例えば、「2025-01-19 のログだけを対象に、ユーザーごとの合計時間を出し、その中で 60 分以上のユーザーだけを表示したい」とします。

日本語で分解すると、

2025-01-19 の行だけを対象にする
ユーザーごとに minutes を合計する
合計が 60 分以上のユーザーだけに絞る

これを SQL にすると、こうなります。

SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
WHERE date = '2025-01-19'
GROUP BY user_name
HAVING SUM(minutes) >= 60;
SQL

処理の流れをもう一度言葉で追うと、

FROM で study_logs を読む
WHERE date = ‘2025-01-19’ で、その日の行だけに絞る
GROUP BY user_name で、ユーザーごとにグループを作る
SUM(minutes) で各ユーザーの合計時間を計算する
HAVING SUM(minutes) >= 60 で、合計 60 分以上のユーザーだけに絞る

WHERE は「どの行を集計の対象にするか」を決める。
HAVING は「集計した結果のどのグループを残すか」を決める。
この役割分担を意識すると、どこに何を書くべきか迷いにくくなります。


HAVING でよくやるパターンを具体例で見る

例1:カテゴリごとの商品数が 10 個以上のカテゴリだけを出す

products テーブルがあるとします。

products テーブル

id | category | name      | price
---+----------+-----------+------
 1 | book     | A本       | 1000
 2 | book     | B本       | 1500
 3 | food     | りんご    | 200
 4 | food     | バナナ    | 150
...

「カテゴリごとの商品数を数えつつ、商品数が 10 個以上のカテゴリだけを知りたい」とします。
このときの SQL はこうです。

SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) >= 10;
SQL

ここで HAVING COUNT(*) >= 10 がやっているのは、
「カテゴリごとに数えた結果に対してフィルターをかける」ことです。

WHERE では COUNT(*) は使えないので、
こういう「グループごとの件数で絞りたい」場面では必ず HAVING が登場します。

例2:ユーザーごとの平均勉強時間が 40 分以上のユーザーだけ

study_logs に戻って、「ユーザーごとの平均勉強時間が 40 分以上の人だけ知りたい」とします。

日本語で分解すると、

ユーザーごとに minutes の平均を計算する
平均が 40 分以上のユーザーだけに絞る

SQL はこうなります。

SELECT user_name, AVG(minutes) AS avg_minutes
FROM study_logs
GROUP BY user_name
HAVING AVG(minutes) >= 40;
SQL

AVG(minutes) も集計関数なので、
WHERE ではなく HAVING に書く必要があります。


HAVING を使うときにハマりやすいポイント

「本当は WHERE でいいのに HAVING を使ってしまう」パターン

HAVING は便利ですが、「何でもかんでも HAVING に書けばいい」というものではありません。
例えば、「2025-01-19 のログだけを対象にしたい」という条件は、
集計前の行に対する条件なので、WHERE に書くべきです。

SELECT user_name, SUM(minutes)
FROM study_logs
WHERE date = '2025-01-19'   -- ここは WHERE
GROUP BY user_name;
SQL

これを HAVING に書いても動くデータベースもありますが、
処理としては「一度全部グループを作ってから、グループの中身を見て絞る」ことになるので、
無駄が多くなります。

基本方針としては、

「行そのものの条件」 → WHERE
「集計結果に対する条件」 → HAVING

と分けて考えるのが筋が良いです。

SELECT で付けた別名を HAVING で使えるかどうか

さきほどの例で、こう書きました。

SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name
HAVING SUM(minutes) >= 100;
SQL

ここで、「HAVING total_minutes >= 100」と書けるかどうかは、
データベースによって挙動が違います。

書ける DB もあれば、書けない DB もあります。
初心者のうちは、「HAVING の中では素直に集計関数を書いておく」と決めてしまった方が混乱しません。


実務的な HAVING の使いどころと考え方

「ランキングや条件付き集計」でよく出てくる

HAVING がよく使われるのは、次のような場面です。

ユーザーごとの合計金額が一定以上のユーザーだけを抽出したい
カテゴリごとの商品数が 0 ではないカテゴリだけを出したい
日付ごとのアクセス数が 1000 を超えた日だけを知りたい

例えば、「1 ヶ月の合計購入金額が 1 万円以上のユーザーだけにクーポンを配りたい」といった要件は、
まさに HAVING の出番です。

SELECT user_id, SUM(amount) AS total_amount
FROM orders
WHERE order_date BETWEEN '2025-01-01' AND '2025-01-31'
GROUP BY user_id
HAVING SUM(amount) >= 10000;
SQL

WHERE で「対象期間の注文だけ」に絞り、
GROUP BY でユーザーごとに合計し、
HAVING で「1 万円以上のユーザーだけ」に絞る、という流れです。

「日本語で条件を言ってから、WHERE と HAVING に分解する」

HAVING を使うときは、いきなり SQL で考えず、
まず日本語で条件を言ってみるのがおすすめです。

例えば、

「2025-01-19 のログだけを対象に、ユーザーごとの合計時間を出し、その中で 60 分以上のユーザーだけを表示したい」

これを分解すると、

2025-01-19 の行だけを対象にする → 行の条件 → WHERE
ユーザーごとに minutes を合計する → GROUP BY + SUM
合計が 60 分以上のユーザーだけに絞る → 集計結果の条件 → HAVING

という対応になります。

この「日本語 → WHERE / GROUP BY / HAVING へのマッピング」が自然にできるようになると、
HAVING はもう怖くなくなります。


まとめ(HAVING は「集計したあとに、グループをふるいにかける場所」)

HAVING を初心者目線で整理すると、こうなります。

GROUP BY でグループを作り、COUNT / SUM / AVG などの集計関数で「グループごとの値」を計算したあと、その結果に対して条件をかけるのが HAVING。
WHERE は「グループを作る前の行」に対する条件で、集計関数は使えない。HAVING は「グループを作った後の集計結果」に対する条件で、集計関数を使える。
典型的な使い方は「合計が〇〇以上のグループだけ」「件数が 0 ではないグループだけ」「平均が△△以上のグループだけ」といった“条件付き集計”。
考えるときは、まず日本語で「行の条件」と「集計結果の条件」を分けて言語化し、「行の条件は WHERE」「集計結果の条件は HAVING」に振り分けるとスムーズ。

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