タイトル: HAVING句
SEOタイトル: SQL HAVING 句完全ガイド (WHERE との違い)
| この記事の要点 |
|
HAVING 句とは
HAVING 句は GROUP BY でグループ化した結果に対して、集計値を条件にフィルタリングするための SQL 句です。WHERE 句が個々の行に対する条件なのに対し、HAVING はグループ全体に対する条件を書きます。
基本構文
SELECT カラム, 集計関数(...)
FROM テーブル
[WHERE 行のフィルタ]
GROUP BY カラム
[HAVING 集計後のフィルタ]
[ORDER BY ...]
[LIMIT ...]
SQL の処理順序は次のとおりです:
FROMでテーブルを読むWHEREで行をフィルタGROUP BYでグループ化HAVINGでグループをフィルタSELECTで列を計算ORDER BY/LIMIT
WHERE と HAVING の違い
| 項目 | WHERE | HAVING |
|---|---|---|
| 対象 | 個々の行 | グループ(集計結果) |
| 適用タイミング | GROUP BY の前 | GROUP BY の後 |
| 集計関数 | 使えない | 使える |
| インデックス利用 | 可能(高速) | 不可(集計後) |
| 必須条件 | 単独で使える | 通常 GROUP BY 必須 |
典型例: 注文 5 件以上のユーザー
-- ユーザーごとの注文数を出し、5 件以上のユーザーだけ抽出
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5
ORDER BY order_count DESC;
-- 結果例
-- user_id | order_count
-- 42 | 12
-- 7 | 8
-- 128 | 5
WHERE と HAVING を併用
「2024 年以降の注文に絞り(WHERE)、ユーザーごとに集計し、合計金額 10 万超のみ(HAVING)」のように両方を使うのが定石:
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE ordered_at >= '2024-01-01' -- 集計前: インデックス使える
GROUP BY user_id
HAVING SUM(amount) > 100000 -- 集計後: SUM を条件に
ORDER BY total_amount DESC
LIMIT 10;
集計関数を HAVING で使う
-- COUNT
HAVING COUNT(*) > 5
HAVING COUNT(DISTINCT product_id) >= 3
-- SUM / AVG
HAVING SUM(amount) > 100000
HAVING AVG(score) >= 80
-- MIN / MAX
HAVING MIN(price) < 1000
HAVING MAX(created_at) >= '2024-01-01'
-- 複数条件 (AND / OR)
HAVING COUNT(*) > 5 AND SUM(amount) > 50000
HAVING SUM(amount) > 100000 OR COUNT(*) > 10
JOIN との組み合わせ
-- ユーザー名と注文合計を出し、合計 5 万超のみ
SELECT
u.id,
u.name,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE u.status = 'active' -- WHERE: ユーザー行のフィルタ
AND o.status = 'completed' -- WHERE: 注文行のフィルタ
GROUP BY u.id, u.name
HAVING SUM(o.amount) >= 50000 -- HAVING: 集計後のフィルタ
ORDER BY total_amount DESC;
サブクエリ vs HAVING
同じ結果を得るのに、HAVING を使う方法とサブクエリで書く方法があります:
-- 方法 A: HAVING
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5;
-- 方法 B: サブクエリ (派生テーブル)
SELECT user_id, cnt
FROM (
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
) t
WHERE cnt >= 5;
-- 方法 C: WHERE 内のサブクエリ
SELECT user_id
FROM users
WHERE id IN (
SELECT user_id FROM orders
GROUP BY user_id HAVING COUNT(*) >= 5
);
多くの DB ではオプティマイザが同等に書き換えるため性能差はわずかですが、可読性は HAVING の方が高いです。
性能上の注意: WHERE で絞れるなら WHERE で
-- ❌ 非効率: 全行を集計してから「2024 年以降」を絞る
SELECT user_id, MAX(ordered_at)
FROM orders
GROUP BY user_id
HAVING MAX(ordered_at) >= '2024-01-01';
-- ✅ 効率的: WHERE で 2024 年以降に絞ってから集計
-- (ordered_at にインデックスがあれば爆速)
SELECT user_id, MAX(ordered_at)
FROM orders
WHERE ordered_at >= '2024-01-01'
GROUP BY user_id;
-- ※ ただし「過去にしか注文してないユーザー」は出ない点に注意
-- 集計後でしか判定できない場合のみ HAVING
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 100; -- COUNT は集計後にしか分からない
HAVING を GROUP BY 無しで使う
標準 SQL では HAVING は GROUP BY なしでも使えます(全行が 1 グループとして扱われる)が、実用上はほぼ使いません:
-- 全件の COUNT が 100 を超えていれば 1 行返る
SELECT COUNT(*) AS total
FROM users
HAVING COUNT(*) > 100;
-- 実際にはこう書く方が分かりやすい
SELECT COUNT(*) AS total FROM users;
-- → 結果を見て判断
DB ごとの互換性
| DB | HAVING 対応 | 備考 |
|---|---|---|
| MySQL / MariaDB | 標準互換 | SELECT のエイリアスを HAVING で使える(標準外) |
| PostgreSQL | 標準互換 | 厳密。エイリアスを HAVING で使うのは要 LATERAL 等 |
| SQL Server | 標準互換 | SELECT エイリアス HAVING 使用不可 |
| Oracle | 標準互換 | SELECT エイリアス HAVING 使用不可 |
| SQLite | 標準互換 | 緩い(GROUP BY 無し HAVING も寛容) |
FAQ
Q: WHERE に集計関数を書ける?
A: 書けません。WHERE COUNT(*) > 5 はエラーになります。集計関数は HAVING で。
Q: HAVING でカラムエイリアスを使える?
A: MySQL は使えますが、PostgreSQL / SQL Server / Oracle は使えません。集計式そのものを書くのが互換性のためにはベストです。
Q: HAVING は遅い?
A: HAVING 自体は遅くありません。ただし WHERE で先に絞れる条件を HAVING に書くと、無駄なグループ化が発生して遅くなります。