MySQLのHAVING句は、GROUP BYによる集計後に各グループに対して条件を適用するための構文です。対してWHERE句は、集計の前段階で個々の行をフィルタリングします。この根本的な実行タイミングの違いが、両者の用途と制約を決定づけます。
基本的な動作の違い
- WHERE:テーブルから読み込まれた直後の行に対して評価。集計関数(例:
COUNT(),SUM(),AVG())を含めることはできません。 - HAVING:
GROUP BYによるグループ化と集計処理が完了した後、各グループ単位で評価。集計結果を条件に使用できます。
実用例で理解する
例1:地域ごとの人口合計と面積合計を取得
SELECT region, SUM(population) AS total_pop, SUM(area) AS total_area
FROM world_countries
GROUP BY region;
このクエリは、region列でレコードをグループ化し、それぞれのグループについて人口と面積の合計を計算します。
例2:面積合計が100万平方キロメートルを超える地域のみを抽出
SELECT region, SUM(population) AS total_pop, SUM(area) AS total_area
FROM world_countries
GROUP BY region
HAVING total_area > 1000000;
WHERE area > 1000000では意図通りの結果が得られません。なぜなら、元のテーブルには「地域全体の面積」というカラムは存在せず、それはSUM(area)という集計によって初めて生成される値だからです。
重複データの検出
メールアドレスの重複を特定するケース:
SELECT email, COUNT(*) AS occurrence_count
FROM user_profiles
GROUP BY email
HAVING occurrence_count > 1;
このクエリは、まず全ユーザーをemailでグループ化し、次に各グループの件数をカウント。最後に、そのカウント値が2以上であるグループのみを返します。
文字列長のチェック(補足)
集計とは無関係ですが、よく併用される文字列操作の例として:
SELECT url FROM websites
WHERE CHAR_LENGTH(TRIM(url)) BETWEEN 2 AND 9;
これはWHEREで十分なケースであり、HAVINGは不要です。
ORDER BYとの位置関係
HAVINGはGROUP BYの直後、かつORDER BYの前に記述しなければなりません。以下の順序は必須です:
GROUP BYHAVINGORDER BY
誤った順序(例:GROUP BY ... ORDER BY ... HAVING)は構文エラー(エラー1064)を引き起こします。
性能上の考慮
可能な限りフィルタリングはWHEREで行うべきです。例えば、特定の都市名を持つレコードだけを集計対象にしたい場合:
-- 効率的:集計前に不要な行を除外
SELECT city, AVG(temp_lo)
FROM weather
WHERE city IN ('Tokyo', 'Osaka')
GROUP BY city;
-- 非効率的:全レコードを集計後に絞り込む
SELECT city, AVG(temp_lo)
FROM weather
GROUP BY city
HAVING city IN ('Tokyo', 'Osaka');
HAVINGは集計済みの結果セットに対して作用するため、WHEREよりも遅い可能性があります。不要な集計処理を減らすためにも、事前の行フィルタリングを優先しましょう。