SQLのHAVING句は、GROUP BYでまとめたグループのうち、条件に合うグループだけを残すために用いられます。COUNTやSUMなどで集計した結果を条件にできる点が、行を絞り込むWHERE句との大きな違いです。たとえば、注文回数が3回以上の顧客や、売上の合計が1万円以上の商品カテゴリだけを取り出したいときに使います。
HAVING句とは
HAVINGは英語で「持っている」という意味で、指定した条件を持つグループだけを結果に残す句です。SELECT文を構成する句のなかで、HAVING句は不要なグループを取り除く役割を担っています。
| 句 | 役割 |
| SELECT | 結果に表示するカラムを決める |
| FROM | データを取得するテーブルを指定する |
| WHERE | 条件に合わない行を取り除く |
| GROUP BY | 同じ値を持つ行をグループにまとめる |
| HAVING | 条件に合わないグループを取り除く |
| ORDER BY | 結果を並べ替える |
HAVING句はSELECT文で使う句で、GROUP BY句のあとに書きます。UPDATE文やDELETE文には、HAVING句はありません。
【基本構文】
SELECT カラム名, 集計関数(カラム名)
FROM テーブル名
GROUP BY カラム名
HAVING 条件;
GROUP BYでグループにまとめたあと、HAVINGの条件を満たすグループだけが結果に残ります。条件には「COUNT(*) >= 3」のように、COUNT・SUM・AVG・MAX・MINといった集計関数を使うのが基本です。構文の詳細は、MySQL公式リファレンスマニュアルのSELECTステートメントで確認できます。
なお、SQLは書いた順番どおりに処理されるわけではありません。HAVING句を含むSELECT文は、次の順番で処理されます。
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
WHERE句はグループを作る前、HAVING句はグループを作ったあとに処理されます。この順番が、WHERE句とHAVING句を使い分けるときのポイントです。GROUP BY句の使い方は、以下の記事で解説しています。
HAVING句の基本的な使い方
【サンプルテーブル】
HAVING句の具体的な使い方を解説していくにあたって、サンプルとして、ネットショップの注文を記録した「注文(orders)」テーブルを用意しました。1行が1件の注文を表しています。
「注文(orders)」テーブル
| 注文ID(id) | 顧客名(customer) | カテゴリ(category) | 金額(amount) |
| 1 | 佐藤 | 書籍 | 1200 |
| 2 | 佐藤 | 家電 | 15000 |
| 3 | 佐藤 | 書籍 | 800 |
| 4 | 鈴木 | 食品 | 3000 |
| 5 | 鈴木 | 書籍 | 1500 |
| 6 | 高橋 | 家電 | 32000 |
| 7 | 田中 | 食品 | 2500 |
| 8 | 田中 | 食品 | 1800 |
| 9 | 田中 | 書籍 | 900 |
| 10 | 田中 | 家電 | 8000 |
| 11 | 伊藤 | 食品 | 4200 |
| 12 | 伊藤 | 書籍 | 600 |
手元のMySQLで試す場合は、次のクエリでテーブルを作成し、データを登録してください。
【テーブル作成用クエリ】
CREATE TABLE orders(
id INT PRIMARY KEY,
customer VARCHAR(20) NOT NULL,
category VARCHAR(20) NOT NULL,
amount INT NOT NULL
);
INSERT INTO orders (id,customer,category,amount) VALUES
(1,'佐藤','書籍',1200),(2,'佐藤','家電',15000),(3,'佐藤','書籍',800),
(4,'鈴木','食品',3000),(5,'鈴木','書籍',1500),
(6,'高橋','家電',32000),
(7,'田中','食品',2500),(8,'田中','食品',1800),(9,'田中','書籍',900),(10,'田中','家電',8000),
(11,'伊藤','食品',4200),(12,'伊藤','書籍',600);
件数で絞り込む
まずはGROUP BYだけを使って、顧客ごとの注文回数を数えてみます。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
GROUP BY customer;
【実行結果】
| customer | 注文回数 |
| 佐藤 | 3 |
| 鈴木 | 2 |
| 高橋 | 1 |
| 田中 | 4 |
| 伊藤 | 2 |
ここから、注文回数が3回以上の顧客だけを残します。グループごとの件数を条件にするので、HAVING句に「COUNT(*) >= 3」と書きます。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 3;
【実行結果】
| customer | 注文回数 |
| 佐藤 | 3 |
| 田中 | 4 |
注文回数が3回未満の鈴木さん・高橋さん・伊藤さんのグループがまとめて取り除かれ、佐藤さんと田中さんだけが残りました。COUNT関数の使い方は、以下の記事で解説しています。
合計金額で絞り込む
HAVING句の条件には、COUNT以外の集計関数も使えます。顧客ごとの購入金額の合計が10,000円以上の顧客を取り出します。
【実行コード】
SELECT customer, SUM(amount) AS 合計金額
FROM orders
GROUP BY customer
HAVING SUM(amount) >= 10000;
【実行結果】
| customer | 合計金額 |
| 佐藤 | 17000 |
| 高橋 | 32000 |
| 田中 | 13200 |
高橋さんは注文が1回だけですが、合計金額が32,000円なので残っています。同じように、平均(AVG)・最大値(MAX)・最小値(MIN)なども条件にできます。
複数の条件を組み合わせる
HAVING句でも、WHERE句と同じようにANDやORで条件を組み合わせられます。注文回数が2回以上で、かつ合計金額が5,000円以上の顧客を取り出します。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数, SUM(amount) AS 合計金額
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 2 AND SUM(amount) >= 5000;
【実行結果】
| customer | 注文回数 | 合計金額 |
| 佐藤 | 3 | 17000 |
| 田中 | 4 | 13200 |
鈴木さん(2回・4,500円)と伊藤さん(2回・4,800円)は、注文回数の条件は満たしていますが、合計金額が5,000円に届かないため取り除かれました。
絞り込んだ結果を並べ替える
HAVING句で絞り込んだ結果を並べ替えたいときは、HAVING句のあとにORDER BY句を書きます。合計金額が10,000円以上の顧客を、合計金額の多い順に表示します。
【実行コード】
SELECT customer, SUM(amount) AS 合計金額
FROM orders
GROUP BY customer
HAVING SUM(amount) >= 10000
ORDER BY 合計金額 DESC;
【実行結果】
| customer | 合計金額 |
| 高橋 | 32000 |
| 佐藤 | 17000 |
| 田中 | 13200 |
ORDER BY句はHAVING句よりもあとに処理されるため、HAVING句で残ったグループだけが並べ替えの対象になります。ORDER BY句の使い方は、以下の記事で解説しています。
「WHERE」句と「HAVING」句の違い
WHERE句とHAVING句は、どちらもデータを絞り込むための句です。違いは、何を、いつ絞り込むかにあります。
| 項目 | WHERE句 | HAVING句 |
| 絞り込む対象 | 1行ずつのデータ | GROUP BYでまとめたグループ |
| 処理されるタイミング | GROUP BYの前 | GROUP BYのあと |
| 集計関数(COUNT・SUMなど) | 条件に使えない | 条件に使える |
| 書く位置 | FROM句のあと | GROUP BY句のあと |
テーブルに保存されている1行ずつのデータを条件にするならWHERE句、グループごとの集計結果を条件にするならHAVING句と覚えておきましょう。
WHERE句はグループ化の前に行を絞り込む
WHERE句は、グループにまとめる前の1行ずつのデータに条件をかけます。1,000円以上の注文だけを対象にして、顧客ごとの注文回数を数えてみましょう。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
WHERE amount >= 1000
GROUP BY customer;
【実行結果】
| customer | 注文回数 |
| 佐藤 | 2 |
| 鈴木 | 2 |
| 高橋 | 1 |
| 田中 | 3 |
| 伊藤 | 1 |
1,000円未満の注文(佐藤さんの800円、田中さんの900円、伊藤さんの600円)が先に取り除かれてから数えているため、GROUP BYだけのときより、佐藤さん・田中さん・伊藤さんの回数が1回ずつ減っています。
HAVING句はグループ化の後にグループを絞り込む
HAVING句は、GROUP BYでまとめたあとのグループに条件をかけます。すべての注文を対象に、注文回数が2回以上の顧客を取り出してみましょう。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 2;
【実行結果】
| customer | 注文回数 |
| 佐藤 | 3 |
| 鈴木 | 2 |
| 田中 | 4 |
| 伊藤 | 2 |
注文が1回だけの高橋さんのグループが取り除かれました。HAVING句は行を1件ずつ見るのではなく、グループ単位で残すかどうかを判定しています。
WHEREとHAVINGは併用できる?
WHERE句とHAVING句は処理されるタイミングが異なるため、1つのSELECT文で併用できます。1,000円以上の注文に絞ってから顧客ごとに数え、注文回数が2回以上の顧客だけを残してみましょう。
【実行コード】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
WHERE amount >= 1000 -- ①1,000円以上の注文だけにする
GROUP BY customer -- ②顧客ごとにまとめる
HAVING COUNT(*) >= 2; -- ③注文回数が2回以上のグループだけ残す
【実行結果】
| customer | 注文回数 |
| 佐藤 | 2 |
| 鈴木 | 2 |
| 田中 | 3 |
伊藤さんは注文が2回ありますが、600円の注文がWHERE句で先に取り除かれて1回になったため、HAVING句の条件を満たさず結果から外れました。
このように、WHERE句はテーブルに保存されているデータに、HAVING句はグループにまとめたデータに条件をかけます。GROUP BYを含むSELECT文に条件を追加するときは、その条件がどちらに対するものかを考えてから書く場所を決めましょう。
HAVING句でよくあるエラーと対処法
HAVING句を使い始めたころによく出るエラーと、その直し方を紹介します。エラーメッセージはMySQLのもので、エラー番号ごとの意味はMySQL公式のサーバーエラーメッセージリファレンス(英語)で調べられます。
WHERE句に集計関数を書いている
注文回数のような集計結果の条件をWHERE句に書くと、エラーになります。
【間違った例】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
WHERE COUNT(*) >= 3
GROUP BY customer;
-- ERROR 1111 (HY000): Invalid use of group function
「集計関数の使い方が正しくない」という意味のエラーです。WHERE句が処理される時点では、まだグループが作られていないため、COUNT(*)でグループごとの件数を数えられません。集計結果の条件はHAVING句に書きます。WHERE句とHAVING句を併用する場合も、集計関数を使う条件だけはHAVING句に移しましょう。
グループにまとめていないカラムを条件にしている
HAVING句に、GROUP BYで指定していないカラムをそのまま書くと、エラーになります。
【間違った例】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
GROUP BY customer
HAVING amount >= 1000;
-- ERROR 1054 (42S22): Unknown column 'amount' in 'having clause'
HAVING句が処理されるのはグループにまとめたあとなので、注文1件ごとの金額(amount)は参照できません。HAVING句の条件には、GROUP BYで指定したカラムか、集計関数を使います。注文1件ごとの金額で絞り込みたい場合は、WHERE句に書きましょう。
WHERE句でSELECT句の別名を使っている
SELECT句で付けた別名(AS 注文回数)を、WHERE句の条件に使うとエラーになります。
【間違った例】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
WHERE 注文回数 >= 3
GROUP BY customer;
-- ERROR 1054 (42S22): Unknown column '注文回数' in 'where clause'
WHERE句はSELECT句よりも先に処理されるため、その時点では別名がまだ決まっていません。この例は件数の条件なので、「HAVING COUNT(*) >= 3」と書き直します。
句を書く順番が違う
HAVING句をGROUP BY句より前に書くと、構文エラーになります。
【間違った例】
SELECT customer, COUNT(*) AS 注文回数
FROM orders
HAVING COUNT(*) >= 3
GROUP BY customer;
-- ERROR 1064 (42000): You have an error in your SQL syntax; ... near 'GROUP BY customer' at line 4
句は「SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY」の順に書きます。エラーメッセージの「near」のあとには、問題が見つかった箇所が表示されるので、その前後の句の順番を確認しましょう。
HAVING句の応用
全体の平均と比べて絞り込む
HAVING句の条件には、サブクエリ(SELECT文の中に書いたSELECT文)の結果も使えます。顧客ごとの合計金額が、全顧客の平均を上回る顧客を取り出します。
【実行コード】
SELECT customer, SUM(amount) AS 合計金額
FROM orders
GROUP BY customer
HAVING SUM(amount) > (
SELECT AVG(total)
FROM (SELECT SUM(amount) AS total FROM orders GROUP BY customer) AS t
);
【実行結果】
| customer | 合計金額 |
| 佐藤 | 17000 |
| 高橋 | 32000 |
カッコ内のサブクエリで、顧客ごとの合計金額(17,000円・4,500円・32,000円・13,200円・4,800円)の平均である14,300円を求めています。外側のHAVING句で、合計金額がこの値より大きいグループだけが残りました。基準になる値を自分で決めずに、データから求めた値と比べたいときに使える書き方です。
種類の数で絞り込む
COUNTの中にDISTINCTを書くと、重複を除いた種類の数を条件にできます。3つ以上のカテゴリの商品を購入した顧客を取り出します。
【実行コード】
SELECT customer, COUNT(DISTINCT category) AS 購入カテゴリ数
FROM orders
GROUP BY customer
HAVING COUNT(DISTINCT category) >= 3;
【実行結果】
| customer | 購入カテゴリ数 |
| 田中 | 3 |
田中さんは食品・書籍・家電の3つのカテゴリを購入しているため残りました。佐藤さんは注文が3回ありますが、書籍と家電の2種類なので条件を満たしません。DISTINCTの使い方は、以下の記事で解説しています。
WHEREとHAVINGで同じ結果になるときはどちらを使う?
GROUP BYで指定したカラム(この例ではcustomer)の条件は、WHERE句とHAVING句のどちらに書いても同じ結果になります。次の2つは、どちらも高橋さん以外の顧客の注文回数を求めるSQLです。
【実行コード】
-- WHERE句で絞り込む
SELECT customer, COUNT(*) AS 注文回数
FROM orders
WHERE customer <> '高橋'
GROUP BY customer;
-- HAVING句で絞り込む
SELECT customer, COUNT(*) AS 注文回数
FROM orders
GROUP BY customer
HAVING customer <> '高橋';
【実行結果】(どちらも同じ)
| customer | 注文回数 |
| 佐藤 | 3 |
| 鈴木 | 2 |
| 田中 | 4 |
| 伊藤 | 2 |
このように同じ結果になる場合は、WHERE句を使いましょう。WHERE句なら、グループにまとめる前に不要な行を減らせるため、集計する行が少なくて済みます。HAVING句に書くと、いったんすべての行をグループにまとめてから取り除くことになり、データが多いほど無駄な処理が増えます。
「1行ずつのデータの条件はWHERE句、集計結果の条件はHAVING句」と使い分けると、読みやすく効率のよいSQLになります。
HAVING句に関するよくある質問
Q. GROUP BYを書かずにHAVING句だけを使える?
A. 使えます。GROUP BYがない場合は、テーブル全体が1つのグループとして扱われます。次のSQLは、注文が10件以上あるときだけ、注文件数と合計金額を表示します。
SELECT COUNT(*) AS 注文件数, SUM(amount) AS 合計金額
FROM orders
HAVING COUNT(*) >= 10;
| 注文件数 | 合計金額 |
| 12 | 71500 |
条件を「HAVING COUNT(*) >= 20」に変えると、条件を満たさないため1行も返されません。なお、MySQLでは「SELECT * FROM orders HAVING category = ‘家電’;」のように、集計関数を使わない条件をGROUP BYなしのHAVING句に書いても動きます。ただしこれはMySQL独自の動きで、PostgreSQLやSQL Serverではエラーになります。行の条件はWHERE句に書きましょう。
Q. HAVING句でSELECT句の別名は使える?
A. MySQLでは「HAVING 注文回数 >= 3」のように別名を使えます(MySQL公式リファレンスのSELECTステートメントで、標準SQLに対するMySQLの拡張として説明されています)。ただし、PostgreSQLやSQL Serverでは使えません。ほかのデータベースでも動くSQLにしたい場合は、「HAVING COUNT(*) >= 3」のように集計関数をそのまま書くのが確実です。
Q. GROUP BYやHAVINGを使ったビューでデータを更新できる?
A. できません。集計関数・GROUP BY・HAVINGを使ったビューは、ビューの1行が元のテーブルの1行と対応しないため、UPDATE文やINSERT文の対象にできません。MySQLで更新しようとすると「ERROR 1288 (HY000): The target table … of the UPDATE is not updatable」というエラーになります。データを変更するときは、元のテーブルに対してUPDATE文を実行しましょう。
まとめ
今回はHAVING句を紹介しました。HAVING句は「SELECT … FROM … GROUP BY … HAVING 条件;」の形で、GROUP BYでまとめたグループのうち、条件に合うグループだけを残します。
1行ずつのデータを絞り込むならWHERE句、COUNTやSUMなどの集計結果で絞り込むならHAVING句と使い分けましょう。集計関数をWHERE句に書くとエラーになる点にも注意してください。
グループごとの集計そのものについては、以下の記事で詳しく解説しています。
以下では、SQLに関するさまざまな記事を取り上げています。SQLに関する疑問や興味がある方は、ぜひ併せて参考にしてみてください。
SQL関連記事
- RDBを操作するデータベース言語「SQL」とは?
- RDB以外のデータベース管理システム「NoSQL」とは?
- RDB管理システム「MySQL」とは?
- RDB管理システム「PostgreSQL」とは?
- RDB管理システム「SQL Server」とは?
- RDB管理システム「SQLite」とは?
- ブラウザ上でMySQLを管理できる「phpMyAdmin」とは?
- データを抽出する「SELECT」文
- データを追加する「INSERT」文
- データを削除する「DELETE」文
- データを更新する「UPDATE」文
- テーブル構造を変更する「ALTER TABLE」文
- 抽出条件を指定する「WHERE」句
- 抽出条件を指定する「HAVING」句
- 複数の抽出条件をまとめる「IN」句
- 重複レコードを除外する「DISTINCT」
- 抽出範囲を指定する「BETWEEN」演算子
- 抽出条件を満たすレコードの存在の有無を調べる「EXISTS」句
- レコード件数を取得する集計関数「COUNT」関数
- レコードをグループ化する「GROUP BY」句
- 抽出結果を並び替える「ORDER BY」句
- テーブル間のデータを結合する「JOIN」句
- 内部結合「INNER JOIN」句
- 抽出結果を統合して表示する「UNION」句
- データの曖昧検索「LIKE」句
- データの取得件数を制限する「LIMIT」句
- 条件分岐処理「CASE」式
- 「NULL」の扱い方
- 「主キー」とは?
- 「外部キー」とは?
SQLの勉強方法は?
SQLには、今回紹介したHAVING以外にも多くの構文があり、完全に習得し、自在にデータを扱えるようになるには多くの学習時間と実践の時間が必要になります。
特に、実践の時間については独学ではなかなか設けることができないものです。実践を交えてSQLを身につけたいと考えるのであれば、総合的な実践まで経験できるプログラミングスクールがおすすめです。
当サイトTECH MANIAでは、最短3か月で基礎から実践力・応用力まで身につけることのできるプログラミングスクールを開講しています。
<テックマニアの特徴>
・たしかな育成実績と親身な教育 ~セカンドキャリアを全力支援~
・講師が現役エンジニア ~“本当”の開発ノウハウを直に学べる~
・専属講師が学習を徹底サポート ~「わからない」を徹底解決~
・実務ベースでスキルを習得 ~実践的な凝縮カリキュラム~
独学でつまずいた方、一流のITエンジニアとしてスキルを高めていきたい方は必見です。
お問い合わせは以下のリンクからお進みください。
