概要(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;
SQLAVG(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;
SQLWHERE で「対象期間の注文だけ」に絞り、
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」に振り分けるとスムーズ。
