Oracle Database 19c の HAVING句は、集計するグループを条件で絞り込むために使います。WHEREは集計対象の行、HAVINGは集計値やグループの条件を指定します。GROUP BYを省略して、入力全体の集計を条件判定することもできます。
| この記事の要点 |
|
HAVING 句とは
SELECT文の中で、グループごとの件数や合計に条件を付けます。これはSQLのDMLの問合せ例です。Oracle 19cを基準にし、末尾には既存の比較用コードも区別して残しています。Oracle 19c SELECT
基本構文
次は構文の見取り図です。日本語の名前や省略記号は置換する必要があり、そのまま実行するSQLではありません。
SELECT グループ列, 集計式 AS 集計別名
FROM 対象表
WHERE 行の条件
GROUP BY グループ列
HAVING グループの条件
ORDER BY 集計別名 DESC, 一意なグループキー
FETCH FIRST 10 ROWS ONLY;
FROM、WHERE、GROUP BY、HAVING、SELECT、ORDER BY、行数制限という説明順は、結果の意味を考えるための論理モデルです。実際の実行計画の順序や索引の利用を保証するものではありません。19cの件数制限にはFETCH/OFFSETを使います。LIMITを含む旧構文は比較用です。
例で使う表の前提
以下のorders/usersは説明用の表名で、読者のDBに存在すると確認したものではありません。参照権限があり、ordersにはid・user_id・product_idが数値、amount・score・priceがNULL許容の数値、ordered_at・created_atがDATE、statusが文字列、usersには一意なid・name・statusがある教材表を前提にします。user_idはNULLを許す場合を含みます。JOIN例ではusers.idが一意であることが重要です。
旧例のcustomer_id・price・order_dateを使うordersは別の教材契約です。user_id・amount・ordered_atへ黙って読み替えず、必要な列を用意した専用表で比較します。掲載SQLは未実行です。
WHERE と HAVING の違い
WHERE句は、集計に入れる個々の行を選びます。
| 項目 | WHERE | HAVING |
|---|---|---|
| 対象 | 個々の行 | グループまたは入力全体の集計 |
| 適用タイミング | 論理上はグループ化前 | 論理上はグループ化後 |
| 集計関数 | 同じレベルの集計を直接条件にできない。別サブクエリ内の集計は別 | 集計式やグループを決める式を条件にできる |
| インデックス利用 | 存在や選択率などにより計画が決まる | HAVINGがあるだけで索引利用不可にはならない |
| 必須条件 | GROUP BYなしでも使える | GROUP BYなしの全体集計にも使える |
グループ化した問合せで、SELECTやHAVINGに任意の非集約列を置くことはできません。定数やグループキー等の規則に合わせます。非集約の条件をすべて機械的にWHEREへ移すという規則でもありません。
典型例: 注文 5 件以上のユーザー
ordersの行を数えます。NULLのuser_idも、一つのグループになり得ます。結果は実データ次第で、旧例の42/12件、7/8件、128/5件は説明用の数値です。
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5
ORDER BY order_count DESC, user_id;
注文が3件以上ある顧客
旧例の列名customer_idを使う別の教材表が前提です。閾値3は5件例とは別の条件として残します。
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 3;
合計金額が10万円以上の顧客
旧例ではpriceが集計する金額、customer_idが顧客の識別子です。単価なら数量を掛ける等の業務定義が必要で、列名だけから売上の意味を推定しません。
SELECT customer_id, SUM(price) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(price) >= 100000;
WHERE と HAVING を併用
現行例の意味は「2024年以降の注文の合計が10万を超えるグループ」です。Oracleでは暗黙の日付変換に頼らずDATEリテラルを使います。
SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_amount
FROM orders
WHERE ordered_at >= DATE '2024-01-01'
GROUP BY user_id
HAVING SUM(amount) > 100000
ORDER BY total_amount DESC, user_id
FETCH FIRST 10 ROWS ONLY;
WHEREとHAVINGの併用: 2024年だけを対象にする旧例
customer_id・price・order_dateがある別教材表用です。上限も指定して、2025年以降を含めないようにします。旧本文の下限だけのSQLは「2024年だけ」という説明とは一致しません。
SELECT customer_id, SUM(price) AS total
FROM orders
WHERE order_date >= DATE '2024-01-01'
AND order_date < DATE '2025-01-01'
GROUP BY customer_id
HAVING SUM(price) >= 100000
ORDER BY total DESC, customer_id;
集計関数を HAVING で使う
| 関数・式 | 何を集計するか |
|---|---|
| COUNT(*) | NULLを含む行自体の件数 |
| COUNT(DISTINCT col) | NULLを除いた異なる値の件数 |
| SUM(col) | NULL以外の合計 |
| AVG(col) | NULL以外の平均。NULLを0として分母へ加える計算とは異なる |
| MIN(col) / MAX(col) | NULL以外の最小・最大 |
| SUM(CASE WHEN ... THEN 1 ELSE 0 END) | 条件一致を数える式。空入力の全体SUMはNULLになる点に注意 |
COUNT、SUM、AVG、MIN/MAX、DISTINCT product_id、AND/ORの既存条件断片は補足H4に残しています。DATE列のMAXと比較する日付は、Oracle例ではDATE '2024-01-01'と明示します。Oracle 19c aggregate functions
NULLと空集合の違い
COUNT(amount)はNULLを数えず、COUNT(*)はその行も数えます。SUM(amount)がNULLのグループでは、SUM(amount) > 0はTRUEではないためHAVINGを通りません。NULL判定にはIS NULLを使います。Oracle 19c Nulls
次は実表を必要としない、Oracle 19c向けの読取例です。期待値の説明であり、実行出力ではありません。
WITH sample_data (grp, amount) AS (
SELECT 1, CAST(NULL AS NUMBER) FROM dual
UNION ALL
SELECT 1, CAST(NULL AS NUMBER) FROM dual
UNION ALL
SELECT 2, 10 FROM dual
UNION ALL
SELECT 2, CAST(NULL AS NUMBER) FROM dual
)
SELECT grp, COUNT(*) AS row_count, COUNT(amount) AS value_count,
SUM(amount) AS total_amount, AVG(amount) AS avg_amount
FROM sample_data
GROUP BY grp
ORDER BY grp;
grp=1は行数2、値数0、合計/平均NULL、grp=2は行数2、値数1、合計/平均10を期待します。GROUP BY grpの直前へWHERE 1=0を加えればグループ自体がなく0行です。GROUP BYを使わない空入力の全体集計なら、COUNTは0、SUM/AVGはNULLの1行になります。NVL(SUM(amount),0)と、入力側のNVL(amount,0)は適用箇所が異なります。
JOIN との組み合わせ
既存H5のusers.idが一意、ordersの1行が1注文という教材契約なら、activeユーザーのcompleted注文だけを集計し、合計が5万円以上のグループを選びます。SQLの>=50000を「5万超」と説明しません。INNER JOINなので注文のないユーザーは出ません。多対多のJOINで行が増えるとCOUNT/SUMも変わるため、結合後の1行の意味を確認します。
サブクエリ vs HAVING
H6の方法AとBは、同じorders集計をHAVINGで絞るか、集計したインラインビューの外側のWHEREで絞る比較です。別名cntも外側なら通常の列として参照できます。
方法Cはusersを絞る別の目的です。元のSELECT user_id FROM usersは、本文で使うusers.idとは列が一致しません。次はusers.idが一意・非NULLで、対象ユーザーだけを返す修正版です。COUNTを返すA/B、NULLや存在しないユーザーのordersグループまで返すA/Bとは同じ結果形ではありません。
SELECT u.id AS user_id
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
GROUP BY o.user_id
HAVING COUNT(*) >= 5
);
同じSQLへ書き換えられる、性能差は小さい、と一律には言えません。対象・返却列・NULL・制約・実行計画を比較します。
性能上の注意: WHERE で絞れるなら WHERE で
H7にあるMAX(ordered_at)の二つの例は、同じ下限条件とMAXだけを返す場合、条件を満たすグループと最大日時が一致すると考えられます。旧例の「過去だけのユーザーが出ない」は両方に当てはまります。この比較だけからHAVING側を遅いと断定できません。
ただしSUMやCOUNTを追加すると意味が変わります。WHEREで2024年以降へ絞れば、それ以前の行は集計に入りません。「全期間の合計を表示し、直近注文があるユーザーを選ぶ」なら、MAXのHAVINGをWHEREへ移すだけでは同じ結果になりません。索引があるだけで高速化を保証せず、必要な意味を決めてから計画・実測を比較します。
HAVING を GROUP BY 無しで使う
usersの入力全体を一つの集計として扱います。
SELECT COUNT(*) AS total
FROM users
HAVING COUNT(*) > 100;
101件以上なら1行、100件以下なら0行の期待です。HAVINGを外したCOUNTは、入力が空でも0を含む1行を返すため、同じ問合せの代替ではありません。空集合をCOUNT(*)=0で検出する用途もあります。
DB ごとの互換性
| 製品・参照範囲 | HAVING | SELECT別名の扱い |
|---|---|---|
| MySQL 8.4 / MariaDB | 対応 | MySQL8.4は別名を利用可。MariaDB全版をこの確認だけで保証しない |
| PostgreSQL 18 | 対応 | 同じレベルのHAVINGには出力別名を使わず式を記述。インラインビュー等で外へ出せる。LATERAL必須ではない |
| SQL Server | 対応 | 同じレベルのHAVINGにはSELECT別名を使わない |
| Oracle 19c | 対応 | 同じレベルでは式を書く。ORDER BYの別名とは区別 |
| SQLite公式資料 | 対応 | GROUP BYなしの集約問合せにも対応。非集約列をOracleと同じ規則で扱えるとは限らない |
MySQL aliases、PostgreSQL SELECT、SQL Server SELECT、SQLite SELECTを参照してください。
Oracleの後続版の機能資料にはHAVINGの列別名対応が記載されています。「Oracleは全版で不可」とはしません。一方、その仕様を19cへ持ち込まず、本記事では集計式を明示します。正確な初出パッチや利用環境での対応は、対象リリースの資料で確認してください。
FAQ
WHEREに集計関数を書ける? 同じ問合せブロックのWHERE COUNT(*) > 5は使いません。集計を行うサブクエリの結果を外側のWHEREで判定することとは別です。
HAVINGで別名を使える? Oracle19cでは集計式を直接書くか、集計結果をインラインビュー/CTEへ分けます。GROUP BY別名の新機能からHAVINGの全版対応を推定しません。
HAVINGは遅い? 句名だけでは決まりません。意味が同じ範囲で書き方を比較し、データ・索引・計画を確認します。この記事には速度の実測結果はありません。
既存コードの製品別補足
現行8個のpreと、旧v3の4個の表形式コードを、注意付きで残しています。旧コードの出力風コメントは例示であり、実測結果ではありません。
H1
H1: MySQL/PostgreSQL型LIMITを含む旧構文図です。日本語名・角括弧・省略記号を含むため実行不可で、Oracle19cの完成SQLではありません。本文のFETCH表記と区別します。
SELECT カラム, 集計関数(...)
FROM テーブル
[WHERE 行のフィルタ]
GROUP BY カラム
[HAVING 集計後のフィルタ]
[ORDER BY ...]
[LIMIT ...]
H2
H2: orders(user_id)を集計する既存例です。末尾の3行は架空の説明用出力で、観測結果ではありません。order_count同数時を固定するなら本文のORDER BY user_id追加版を使います。
-- ユーザーごとの注文数を出し、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
H3
H3: orders(user_id,amount,ordered_at)を使うMySQL/PG型の比較です。ordered_atの文字列変換に依存し、Oracle19cではLIMITを使えません。本文のDATE/FETCH修正版を使用します。条件は2024年以降で、2024年だけではありません。
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;
H4
H4: 条件式の断片で、そのまま連続実行するSQLではありません。orders等のproduct_id/amount/score/price/created_at列と型が必要です。DATE列との日付比較は本文のDATEリテラルを使います。AND/ORの例は別の条件候補です。
-- 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
H5
H5: users(id,name,status)とorders(id,user_id,amount,status)、一意なusers.id、参照権限を前提とします。>=50000は5万円以上です。INNER JOINの重複・対象外ユーザー・NULLの条件を本文で確認してください。
-- ユーザー名と注文合計を出し、合計 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;
H6
H6: A/Bはorders(user_id)の集計比較です。Cはusersにuser_id列がある場合にしか元のSELECTが成立せず、本文のusers(id)契約とは合いません。Cはusersを選ぶ別の問合せでCOUNTも返しません。修正版は本文のu.id AS user_id/EXISTS例です。無条件に同じ結果・同じ性能とはしません。
-- 方法 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
);
H7
H7: orders(user_id,ordered_at)の旧比較です。旧コメントの非効率/効率的/爆速は実測に基づかず、その性能判定を採用しません。このMAXと下限だけなら両方で過去のみのグループが除かれ、MAXが一致するという意味の比較です。COUNT/SUMを追加すれば同義ではありません。OracleではDATEリテラルを使います。
-- ❌ 非効率: 全行を集計してから「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 は集計後にしか分からない
H8
H8: usersを参照する二つの別問合せです。HAVING付きは条件を満たさないと0行、外したCOUNTは空入力でも1行です。「こう書く方」が同じSQL結果になるわけではありません。
-- 全件の COUNT が 100 を超えていれば 1 行返る
SELECT COUNT(*) AS total
FROM users
HAVING COUNT(*) > 100;
-- 実際にはこう書く方が分かりやすい
SELECT COUNT(*) AS total FROM users;
-- → 結果を見て判断
旧表形式コード L1
旧Oracle構文図。日本語名と省略記号は置換が必要です。
SELECT カラム1, 集約関数(カラム2)
FROM テーブル
WHERE 個別行の条件
GROUP BY カラム1
HAVING 集約後の条件
ORDER BY ...;
|
旧表形式コード L2
customer_id/price列を使う旧教材例です。現行user_id/amount列とは別契約で、観測結果はありません。
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 3;
|
旧表形式コード L3
customer_id/price列を使う旧教材例です。現行user_id/amount列とは別契約で、観測結果はありません。
SELECT customer_id, SUM(price) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(price) >= 100000;
|
旧表形式コード L4
customer_id/price/order_dateの旧例です。2024年だけという旧コメントに反し下限しかありません。本文の上限DATE '2025-01-01'を加えた修正版を使います。
-- 2024年の注文の中で、合計が10万円以上の顧客
SELECT customer_id, SUM(price) AS total
FROM orders
WHERE order_date >= DATE '2024-01-01'
GROUP BY customer_id
HAVING SUM(price) >= 100000
ORDER BY total DESC;
|
子ページはありません
- GROUP BY 句
- HAVING句
- ORDER BY 句
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09