- SQL
SQL HAVINGの使い方|WHEREとの違いとCOUNT条件の絞り込み
HAVINGとは、GROUP BYでまとめたあとの「グループ」を、COUNTやSUMなどの集計結果で絞り込む句です。 「注文が3件以上ある顧客だけ」「合計が1,000以上のグループだけ」のように、集計した値に条件を付けたいときに使います。WHEREが集計する前の「行」を絞るのに対し、HAVINGは集計したあとの「グループ」を絞る、というのがいちばん大きな違いです。
この記事では、MySQL 8.0.46・PostgreSQL 16.15・SQLite 3.45.1で同じSQLを実行し、出力とエラー文をそのまま載せています。SQLが評価される順番を図にしたうえで、WHEREに集計関数を書いたときのエラーをDB別に再現し、HAVINGでSELECTの別名が使えるかどうかの違い、重複検出・条件付き集計・複数条件といった定番パターン、WHEREと併用したときの速度差までを順に説明します。SQL ServerとOracleは手元に環境がないため未実行で、その旨を明記しています。
HAVINGの早見表
まずWHEREとHAVINGの違いです。
| 項目 | WHERE | HAVING |
|---|---|---|
| 絞り込む対象 | 集計する前の行 | 集計したあとのグループ |
| 評価されるタイミング | GROUP BYの前 | GROUP BYの後 |
| 集計関数(COUNT・SUMなど) | 書けない(エラーになる) | 書ける |
| 集計していない列の条件 | 書ける | GROUP BYに含めた列だけ |
| 書く位置 | FROMの後、GROUP BYの前 | GROUP BYの後、ORDER BYの前 |
| 向いている条件 | status = 'done' など行ごとの条件 |
COUNT(*) >= 3 などグループごとの条件 |
次に、やりたいことから書き方を引く表です。どれも今回3つのDBで動かして同じ結果になったものです。
| やりたいこと | 書き方 |
|---|---|
| 件数がn件以上のグループ | GROUP BY customer HAVING COUNT(*) >= 3 |
| 合計がある値以上のグループ | GROUP BY customer HAVING SUM(amount) >= 1000 |
| 重複しているデータを探す | GROUP BY customer, item HAVING COUNT(*) > 1 |
| 1件しかないグループ | GROUP BY customer HAVING COUNT(*) = 1 |
| 条件に合う行を1件以上含むグループ | HAVING SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) >= 1 |
| 条件に合う行を1件も含まないグループ | HAVING SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) = 0 |
| 種類の数で絞る | HAVING COUNT(DISTINCT item) >= 3 |
| 複数の条件を組み合わせる | HAVING COUNT(*) >= 2 AND AVG(amount) < 5000 |
| 全体の平均より大きいグループ | HAVING SUM(amount) > (SELECT AVG(amount) FROM orders) |
| 行を絞ってから集計し、さらに絞る | WHERE status = 'done' GROUP BY customer HAVING SUM(amount) >= 1000 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQL HAVINGの使い方|WHEREとの違いとCOUNT条件の絞り込み」
確認に使った環境とサンプル表
| DB | バージョン | 実行したか |
|---|---|---|
| MySQL | 8.0.46(既定のSQLモード。ONLY_FULL_GROUP_BY を含む) | 実行した |
| PostgreSQL | 16.15 | 実行した |
| SQLite | 3.45.1 | 実行した |
| SQL Server | ― | 未実行(Microsoft LearnのSELECTのページで論理的な処理順序を確認) |
| Oracle Database | ― | 未実行(Oracle Database 23のSQL言語リファレンス SELECT のページを確認) |
サンプルは9件の注文が入った orders 表です。次のSQLは3つのDBでそのまま動きます。
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id INT PRIMARY KEY,
customer VARCHAR(20),
item VARCHAR(20),
amount INT,
status VARCHAR(10)
);
INSERT INTO orders VALUES
(1, 'Aoki', 'pen', 300, 'done'),
(2, 'Aoki', 'note', 500, 'done'),
(3, 'Aoki', 'desk', 12000, 'cancel'),
(4, 'Baba', 'pen', 300, 'done'),
(5, 'Chiba', 'chair', 8000, 'done'),
(6, 'Chiba', 'desk', 12000, 'done'),
(7, 'Chiba', 'pen', 300, 'cancel'),
(8, 'Chiba', 'pen', 300, 'done'),
(9, 'Doi', 'note', 500, 'cancel');
まず、絞り込む前の集計結果を確認しておきます。
SELECT customer, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
GROUP BY customer
ORDER BY customer;
+----------+-----+-------+
| customer | cnt | total |
+----------+-----+-------+
| Aoki | 3 | 12800 |
| Baba | 1 | 300 |
| Chiba | 4 | 20600 |
| Doi | 1 | 500 |
+----------+-----+-------+
顧客ごとに1行、合計4つのグループができました。HAVINGは、この4行のうちどれを残すかを決める句です。
HAVINGの基本(集計結果で絞り込む)
HAVINGはGROUP BYのすぐ後ろに書き、条件には集計関数を使います。
SELECT customer, COUNT(*) AS cnt
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 3
ORDER BY customer;
MySQL 8.0.46 の実行結果です。PostgreSQL・SQLiteでも同じ2行が返りました。
+----------+-----+
| customer | cnt |
+----------+-----+
| Aoki | 3 |
| Chiba | 4 |
+----------+-----+
4つのグループのうち、件数が3以上のAokiとChibaだけが残りました。句を書く順番は次のとおりで、順番を入れ替えると構文エラーになります。
SELECT 列, 集計関数
FROM 表
WHERE 行の条件 -- 省略できる
GROUP BY まとめる列
HAVING グループの条件 -- 省略できる
ORDER BY 並べ替え -- 省略できる
LIMIT 件数; -- 省略できる(MySQL・PostgreSQL・SQLite)
SQLが評価される順番(WHEREとHAVINGの違いの正体)
SQLは書いた順ではなく、次の順番で評価されます。WHEREはGROUP BYより前、HAVINGはGROUP BYより後にあることが、2つの違いをすべて説明してくれます。
flowchart TD
A["1. FROM<br>表から全行を取り出す(9行)"] --> B["2. WHERE<br>行を絞る(集計関数は使えない)"]
B --> C["3. GROUP BY<br>行をグループにまとめる"]
C --> D["4. HAVING<br>グループを絞る(集計関数が使える)"]
D --> E["5. SELECT<br>表示する列を計算し、別名を付ける"]
E --> F["6. ORDER BY<br>並べ替える(別名が使える)"]
F --> G["7. LIMIT<br>件数を切る"]
| 順番 | 句 | この時点で使えるもの |
|---|---|---|
| 1 | FROM | 表の列 |
| 2 | WHERE | 表の列(まだグループがないので集計関数は使えない) |
| 3 | GROUP BY | 表の列 |
| 4 | HAVING | GROUP BYに含めた列と集計関数 |
| 5 | SELECT | GROUP BYに含めた列と集計関数。ここで別名が付く |
| 6 | ORDER BY | SELECTの別名も使える |
| 7 | LIMIT | ― |
この表から、次の3つが読み取れます。
- WHEREに集計関数を書けない:WHEREの時点ではまだグループがなく、数えるものが決まっていないため
- HAVINGに集計していない列を書けない:HAVINGの時点では行がグループにまとまっていて、個々の行の値はもう1つに決まらないため
- HAVINGでSELECTの別名を使えないDBがある:別名が付くのはHAVINGより後のSELECTの段階のため(MySQLとSQLiteは独自に使えるようにしています)
SQL Serverについては、Microsoft LearnのSELECTのページに「論理的な処理順序」として FROM → ON → JOIN → WHERE → GROUP BY → WITH CUBE / WITH ROLLUP → HAVING → SELECT → DISTINCT → ORDER BY → TOP の順が載っており、「SELECT句で定義した列の別名は、それより前の句からは参照できず、ORDER BYのような後の句からは参照できる」と説明されています。
実行計画で2段階の絞り込みを見る
WHEREとHAVINGを両方書いたSQLを、PostgreSQLのEXPLAIN ANALYZE(実際に実行して手順と行数を表示する命令)で見てみます。
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT customer, COUNT(*)
FROM orders
WHERE status = 'done'
GROUP BY customer
HAVING COUNT(*) >= 3;
GroupAggregate (actual rows=1 loops=1)
Group Key: customer
Filter: (count(*) >= 3)
Rows Removed by Filter: 2
-> Sort (actual rows=6 loops=1)
Sort Key: customer
Sort Method: quicksort Memory: 25kB
-> Seq Scan on orders (actual rows=6 loops=1)
Filter: ((status)::text = 'done'::text)
Rows Removed by Filter: 3
下から上に読みます。いちばん下の Seq Scan で WHEREの条件により9行から3行が除かれて6行になり、それをグループにまとめた GroupAggregate で HAVINGの条件により3グループから2グループが除かれて1グループになっています。WHEREは行に、HAVINGはグループに効いていることが、行数の変化からわかります。
WHEREに集計関数を書いたときのエラー(DB別)
HAVINGを覚える前に、多くの人が一度は書くのが次のSQLです。
SELECT customer, COUNT(*)
FROM orders
WHERE COUNT(*) >= 3
GROUP BY customer;
3つのDBでのエラー文です。
-- MySQL 8.0.46
ERROR 1111 (HY000) at line 1: Invalid use of group function
-- PostgreSQL 16.15
ERROR: aggregate functions are not allowed in WHERE
LINE 1: SELECT customer, COUNT(*) FROM orders WHERE COUNT(*) >= 3 GR...
^
-- SQLite 3.45.1
Error: in prepare, misuse of aggregate: COUNT()
| DB | エラー文 | 実行したか |
|---|---|---|
| MySQL 8.0.46 | ERROR 1111 (HY000): Invalid use of group function |
実行した |
| PostgreSQL 16.15 | ERROR: aggregate functions are not allowed in WHERE |
実行した |
| SQLite 3.45.1 | misuse of aggregate: COUNT() |
実行した |
| SQL Server | 集計はWHERE句に書けない、という趣旨のエラーになると一般に知られています | 未実行 |
| Oracle | ORA-00934(グループ関数はここでは使用できない、という趣旨)になると一般に知られています |
未実行 |
どのDBでも、直し方は同じで、条件をHAVINGに移します。
| 直す前 | 直した後 |
|---|---|
WHERE COUNT(*) >= 3 GROUP BY customer |
GROUP BY customer HAVING COUNT(*) >= 3 |
SELECTで付けた別名をWHEREで使っても、同じ理由でエラーになります。
SELECT customer, SUM(amount) AS total
FROM orders
WHERE total >= 1000
GROUP BY customer;
-- MySQL 8.0.46
ERROR 1054 (42S22) at line 1: Unknown column 'total' in 'where clause'
-- PostgreSQL 16.15
ERROR: column "total" does not exist
-- SQLite 3.45.1
Error: in prepare, misuse of aggregate: SUM()
SQLiteは別名 total を SUM(amount) に読み替えたうえで「集計関数の使い方が合っていない」と報告しており、MySQLとPostgreSQLは「そんな列はない」と報告しています。メッセージは違いますが、原因はどれも「WHEREの時点では集計結果がまだない」ことです。
HAVINGでSELECTの別名は使えるか(DB別)
COUNT(*) AS cnt と別名を付けたら、HAVINGでも cnt >= 3 と書きたくなります。これはDBによって結果が分かれます。
SELECT customer, COUNT(*) AS cnt
FROM orders
GROUP BY customer
HAVING cnt >= 3;
-- MySQL 8.0.46(動く)
+----------+-----+
| customer | cnt |
+----------+-----+
| Aoki | 3 |
| Chiba | 4 |
+----------+-----+
-- SQLite 3.45.1(動く)
customer cnt
-------- ---
Aoki 3
Chiba 4
-- PostgreSQL 16.15(エラー)
ERROR: column "cnt" does not exist
LINE 1: ...OUNT(*) AS cnt FROM orders GROUP BY customer HAVING cnt >= 3
^
| DB | HAVINGでSELECTの別名 | 根拠 |
|---|---|---|
| MySQL 8.0.46 | 使える | 実行して確認 |
| SQLite 3.45.1 | 使える | 実行して確認 |
| PostgreSQL 16.15 | 使えない(column "cnt" does not exist) |
実行して確認 |
| SQL Server | 使えない | 未実行。Microsoft Learnに「SELECT句の別名は前の句から参照できない」と記載 |
| Oracle | 使えないと一般に知られています | 未実行。Oracle Database 23のリファレンスには「Release 23からGROUP BY句で別名を使える」と記載があり、開いたページではHAVINGでの別名については確認できませんでした |
集計関数ではない式の別名でも同じでした。SELECT customer, amount*2 AS dbl ... GROUP BY customer, amount HAVING dbl > 10000 は、MySQLとSQLiteでは動き、PostgreSQLでは column "dbl" does not exist になります。
複数のDBで動かすSQLでは、HAVINGには別名ではなく集計関数の式をそのまま書くのが確実です。式が長くて繰り返したくないときは、サブクエリやWITH句(共通テーブル式)で集計結果に名前を付け、外側のWHEREで絞ります。この形は3つのDBすべてで動きました。
WITH t AS (
SELECT customer, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
GROUP BY customer
)
SELECT *
FROM t
WHERE cnt >= 3 AND total >= 10000
ORDER BY customer;
customer | cnt | total
----------+-----+-------
Aoki | 3 | 12800
Chiba | 4 | 20600
(2 rows)
外側のSELECTから見ると cnt と total はふつうの列なので、WHEREで絞れます。なお、ORDER BYでの別名(ORDER BY cnt DESC)は3つのDBすべてで使えました。ORDER BYはSELECTより後に評価されるためです。
HAVINGの定番パターン(実行結果つき)
ここからは、そのままコピーして使えるパターンを結果とともに紹介します。特に断りのないものは、3つのDBで同じ結果になったことを確認しています。
重複しているデータを探す(COUNT(*) > 1)
重複を調べたい列でGROUP BYし、件数が2件以上のグループだけを残します。 「同じ顧客が同じ商品を2回以上注文している」組み合わせを探す例です。
SELECT customer, item, COUNT(*) AS cnt
FROM orders
GROUP BY customer, item
HAVING COUNT(*) > 1;
+----------+------+-----+
| customer | item | cnt |
+----------+------+-----+
| Chiba | pen | 2 |
+----------+------+-----+
重複している行そのもの(idなど)を見たいときは、この結果を元の表に結合します。
SELECT o.*
FROM orders o
JOIN (
SELECT customer, item
FROM orders
GROUP BY customer, item
HAVING COUNT(*) > 1
) d ON d.customer = o.customer AND d.item = o.item
ORDER BY o.id;
id | customer | item | amount | status
----+----------+------+--------+--------
7 | Chiba | pen | 300 | cancel
8 | Chiba | pen | 300 | done
(2 rows)
逆に「1件しかないグループ」は HAVING COUNT(*) = 1 で取り出せます。
WHEREと組み合わせる(行を絞ってから集計する)
「完了(done)した注文だけを対象に、合計が1,000以上の顧客」を求めます。行ごとの条件はWHERE、集計結果の条件はHAVINGと書き分けます。
SELECT customer, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
WHERE status = 'done'
GROUP BY customer
HAVING SUM(amount) >= 1000
ORDER BY customer;
+----------+-----+-------+
| customer | cnt | total |
+----------+-----+-------+
| Chiba | 3 | 20300 |
+----------+-----+-------+
WHEREのあるなしで結果が変わる点に注目してください。WHEREなしではAokiの合計は12,800でしたが、キャンセルの12,000が除かれて800になり、HAVINGの条件から外れました。Chibaも4件・20,600から3件・20,300に変わっています。WHEREは「何を数えるか」を変え、HAVINGは「どのグループを残すか」を決めます。
件数の条件でも同じです。WHERE status = 'done' を付けて HAVING COUNT(*) >= 3 とすると、完了が2件のAokiは外れ、Chiba(3件)だけが返りました。
条件付き集計で絞る(SUM(CASE ...))
「キャンセルが1件以上ある顧客」のように、グループの中の一部の行だけを数えて条件にしたいときは、CASE式を集計関数の中に入れます。WHEREで status = 'cancel' と絞ってしまうと全体の件数がわからなくなるので、この形が役立ちます。
SELECT customer,
SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) AS cancels,
COUNT(*) AS cnt
FROM orders
GROUP BY customer
HAVING SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) >= 1
ORDER BY customer;
+----------+---------+-----+
| customer | cancels | cnt |
+----------+---------+-----+
| Aoki | 1 | 3 |
| Chiba | 1 | 4 |
| Doi | 1 | 1 |
+----------+---------+-----+
条件を = 0 に変えると「キャンセルが1件もない顧客」になり、Babaだけが返りました。割合を条件にすることもできます。
-- キャンセル率が30%以上の顧客
SELECT customer,
100.0 * SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) / COUNT(*) AS cancel_rate
FROM orders
GROUP BY customer
HAVING 100.0 * SUM(CASE WHEN status = 'cancel' THEN 1 ELSE 0 END) / COUNT(*) >= 30
ORDER BY customer;
PostgreSQL 16.15 の実行結果です。Aoki(3件中1件)とDoi(1件中1件)が返りました。
customer | cancel_rate
----------+----------------------
Aoki | 33.3333333333333333
Doi | 100.0000000000000000
(2 rows)
小数の表示桁はDBで異なり、MySQLは 33.33333、SQLiteは 33.3333333333333 でした。100.0 と小数で書いているのは、整数同士の割り算で小数点以下が切り捨てられるDBがあるためです。
PostgreSQLとSQLiteでは、同じことを FILTER 句でも書けます。
-- PostgreSQL・SQLite
SELECT customer, COUNT(*) FILTER (WHERE status = 'cancel') AS cancels
FROM orders
GROUP BY customer
HAVING COUNT(*) FILTER (WHERE status = 'cancel') >= 1;
PostgreSQL 16.15 と SQLite 3.45.1 では Aoki・Chiba・Doi の3行が返り、MySQL 8.0.46 では構文エラー(ERROR 1064)になりました。3つのDBで共通にしたいときは SUM(CASE ...) の形を使います。
複数の条件を組み合わせる(AND・OR・BETWEEN)
HAVINGの条件は、WHEREと同じようにANDやORでつなげます。
-- 2件以上、かつ平均が5,000未満
SELECT customer, COUNT(*) AS cnt, AVG(amount) AS avg_amount
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 2 AND AVG(amount) < 5000
ORDER BY customer;
+----------+-----+------------+
| customer | cnt | avg_amount |
+----------+-----+------------+
| Aoki | 3 | 4266.6667 |
+----------+-----+------------+
Chibaは4件ありますが平均が5,150なので外れ、Aokiだけが残りました。ORの例と、BETWEEN・ORDER BY・LIMITを組み合わせた例も載せます。
-- 1件だけ、または合計が20,000以上
SELECT customer, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
GROUP BY customer
HAVING COUNT(*) = 1 OR SUM(amount) >= 20000
ORDER BY customer;
-- 結果:Baba(1, 300)、Chiba(4, 20600)、Doi(1, 500)
-- 合計が400〜13,000のグループを、合計の大きい順に2件
SELECT customer, SUM(amount) AS total
FROM orders
GROUP BY customer
HAVING SUM(amount) BETWEEN 400 AND 13000
ORDER BY total DESC
LIMIT 2;
-- 結果:Aoki(12800)、Doi(500)
種類の数で絞る(COUNT(DISTINCT ...))
-- 3種類以上の商品を注文した顧客
SELECT customer
FROM orders
GROUP BY customer
HAVING COUNT(DISTINCT item) >= 3;
-- 結果:Aoki、Chiba
-- 2人以上の顧客が注文した商品
SELECT item, COUNT(*) AS cnt, COUNT(DISTINCT customer) AS buyers
FROM orders
GROUP BY item
HAVING COUNT(DISTINCT customer) >= 2
ORDER BY item;
+------+-----+--------+
| item | cnt | buyers |
+------+-----+--------+
| desk | 2 | 2 |
| note | 2 | 2 |
| pen | 4 | 3 |
+------+-----+--------+
penは注文が4件ありますが、Chibaが2回注文しているので顧客の数は3です。COUNT(*) と COUNT(DISTINCT 列) で数えているものが違う点を、結果から確認できます。
指定したものをすべて含むグループを探す
「penとdeskの両方を注文した顧客」は、WHEREで対象の商品に絞り、HAVINGで種類の数が指定した個数と一致するかを調べます。
SELECT customer
FROM orders
WHERE item IN ('pen', 'desk')
GROUP BY customer
HAVING COUNT(DISTINCT item) = 2
ORDER BY customer;
-- 結果:Aoki、Chiba
Babaはpenだけなので種類の数が1になり、外れます。条件の商品を増やしたら、IN の中身と = 2 の数字を一緒に変えます。
最大と最小の差・全体平均との比較
-- 最大と最小の差が10,000以上の顧客
SELECT customer, MAX(amount) AS mx, MIN(amount) AS mn
FROM orders
GROUP BY customer
HAVING MAX(amount) - MIN(amount) >= 10000
ORDER BY customer;
-- 結果:Aoki(12000, 300)、Chiba(12000, 300)
-- 合計が「注文1件あたりの全体平均」より大きい顧客(サブクエリと比較)
SELECT customer, SUM(amount) AS total
FROM orders
GROUP BY customer
HAVING SUM(amount) > (SELECT AVG(amount) FROM orders)
ORDER BY customer;
-- 結果:Aoki(12800)、Chiba(20600)
HAVINGの条件には、SELECTに書いていない集計関数も使えます。1つ目の例でSELECTから MAX・MIN を外しても、HAVINGの条件はそのまま働きます。
GROUP BYなしのHAVING
GROUP BYを省くと、表全体が1つのグループとして扱われます。HAVINGはその1グループを残すかどうかを決めます。
SELECT COUNT(*) AS cnt FROM orders HAVING COUNT(*) > 5; -- 9 が1行返る
SELECT COUNT(*) AS cnt FROM orders HAVING COUNT(*) > 100; -- 0行
3つのDBすべてで、1つ目は 9 の1行、2つ目は0行でした。「件数が一定以上のときだけ結果を返す」という使い方ができます。
HAVINGに書ける列・書けない列
集計していない列を書いたとき
GROUP BYに含めていない列をHAVINGにそのまま書くと、DBによって動きが分かれます。
SELECT customer, COUNT(*) AS cnt
FROM orders
GROUP BY customer
HAVING amount >= 1000;
-- MySQL 8.0.46
ERROR 1054 (42S22) at line 1: Unknown column 'amount' in 'having clause'
-- PostgreSQL 16.15
ERROR: column "orders.amount" must appear in the GROUP BY clause or be used in an aggregate function
-- SQLite 3.45.1(エラーにならず、結果が返る)
customer cnt
-------- ---
Chiba 4
SQLiteはエラーにせず、グループの中のどれか1行の amount を使って判定します。どの行が使われるかは決まっていないので、意図した結果かどうかを結果だけから確かめるのは難しくなります。「1,000以上の注文を含む」なら HAVING MAX(amount) >= 1000、「すべての注文が1,000以上」なら HAVING MIN(amount) >= 1000 のように、集計関数で意図をはっきり書くと、3つのDBで同じ結果になります。
GROUP BYに含めた列の条件はどちらにも書ける
GROUP BYの列そのものに対する条件は、HAVINGにも書けます。
SELECT customer, COUNT(*) AS cnt
FROM orders
GROUP BY customer
HAVING customer <> 'Aoki'
ORDER BY customer;
-- 結果:Baba(1)、Chiba(4)、Doi(1)
ただし、この条件は WHERE customer <> 'Aoki' と書いても同じ結果になります。どちらに書くかで速さが変わるDBがあるので、次の節で実測します。
そのほかのエラー
| 書き方 | MySQL 8.0.46 | PostgreSQL 16.15 | SQLite 3.45.1 |
|---|---|---|---|
集計関数の入れ子 HAVING MAX(COUNT(*)) > 1 |
ERROR 1111: Invalid use of group function |
ERROR: aggregate function calls cannot be nested |
misuse of aggregate function COUNT() |
GROUP BYなしで集計していない列をSELECT SELECT customer, COUNT(*) FROM orders HAVING COUNT(*) >= 3 |
ERROR 1140(ONLY_FULL_GROUP_BYと合わない、という内容) |
column "orders.customer" must appear in the GROUP BY clause ... |
エラーにならず Aoki, 9 の1行 |
集計のないSELECTにHAVING SELECT id, amount FROM orders HAVING amount > 5000 |
動く(id 3, 5, 6 の3行) | column "orders.id" must appear in the GROUP BY clause ... |
HAVING clause on a non-aggregate query |
最後の行のように、MySQLでは集計のないSELECTにもHAVINGを書けて、WHEREのように働きました。ほかのDBでは動かない書き方なので、行の条件はWHEREに書くのが揃えやすい形です。
WHEREとHAVINGの速度差
GROUP BYの列に対する条件は、WHEREにもHAVINGにも書けます。結果は同じでも、WHEREに書けば集計する前に行が減り、HAVINGに書くと全行を集計してから捨てることになるので、速さに差が出る場合があります。100万行の表で確かめました。
-- PostgreSQL:100万行、cat は 0〜999 のカテゴリ番号(各1,000行)
CREATE TABLE items AS
SELECT g AS id, 'item-' || g AS title, ((g::bigint * 7919) % 1000)::int AS cat
FROM generate_series(1, 1000000) g;
CREATE INDEX items_cat ON items (cat);
ANALYZE items;
MySQLとSQLiteにも同じ内容の表を作り、次の2つを比べます。
-- (A) HAVINGで絞る
SELECT cat, COUNT(*) FROM items GROUP BY cat HAVING cat < 10;
-- (B) WHEREで絞る
SELECT cat, COUNT(*) FROM items WHERE cat < 10 GROUP BY cat;
MySQL 8.0.46:WHEREのほうが大幅に速い
-- (A) HAVING
-> Filter: (items.cat < 10) (cost=200156 rows=937) (actual time=0.597..221 rows=10 loops=1)
-> Group aggregate: count(0) (cost=200156 rows=937) (actual time=0.595..221 rows=1000 loops=1)
-> Covering index scan on items using items_cat (cost=100402 rows=997535) (actual time=0.383..163 rows=1e+6 loops=1)
-- (B) WHERE
-> Group aggregate: count(0) (cost=3012 rows=937) (actual time=0.354..3.08 rows=10 loops=1)
-> Filter: (items.cat < 10) (cost=2012 rows=10000) (actual time=0.142..2.53 rows=10000 loops=1)
-> Covering index range scan on items using items_cat over (NULL < cat < 10) (cost=2012 rows=10000) (actual time=0.141..1.71 rows=10000 loops=1)
(A)は100万行(rows=1e+6)を読んで1,000グループを作り、そのあとで10グループに絞っています。(B)はインデックスで1万行だけを読み、10グループを作っています。インデックスを外した状態でも、(A)は372ミリ秒、(B)は224ミリ秒でした。
PostgreSQL 16.15:どちらに書いても同じ実行計画
-- (A) HAVING cat < 10
HashAggregate (actual time=11.155..11.163 rows=10 loops=1)
Group Key: cat
-> Bitmap Heap Scan on items (actual time=1.751..9.773 rows=10000 loops=1)
Recheck Cond: (cat < 10)
-> Bitmap Index Scan on items_cat (actual time=0.967..0.967 rows=10000 loops=1)
Index Cond: (cat < 10)
Execution Time: 11.277 ms
HAVINGに書いた条件が Index Cond として表の読み取りの段階に移されており、(B)も同じ実行計画で 11.502 ms でした。PostgreSQLは、集計関数を含まないHAVINGの条件を自動的にWHERE相当の位置へ移していることが実行計画からわかります。集計関数を含む条件を足した HAVING cat < 10 AND COUNT(*) >= 1000 では、cat < 10 は Index Cond に、count(*) >= 1000 は HashAggregate の Filter に分かれて表示されました。
SQLite 3.45.1:インデックスがあれば同じ読み方
SQLiteでは、cat にインデックスがある状態で(A)(B)とも実行計画が SEARCH items USING COVERING INDEX items_cat (cat<?) となり、どちらも1ミリ秒以下でした。インデックスがない状態では(A)が0.040秒、(B)が0.037秒で、ほぼ同じです。
計測結果のまとめ
| DB | (A) HAVINGで絞る | (B) WHEREで絞る | 読み取り方 |
|---|---|---|---|
| MySQL 8.0.46(インデックスあり) | 221 ms | 3.08 ms | (A)は100万行を集計してから絞る |
| MySQL 8.0.46(インデックスなし) | 372 ms | 224 ms | (A)は1,000グループ、(B)は10グループを作る |
| PostgreSQL 16.15(インデックスあり) | 11.277 ms | 11.502 ms | 同じ実行計画(条件が自動で移される) |
| SQLite 3.45.1(インデックスあり) | 0.000 秒 | 0.001 秒 | どちらもインデックスで範囲を読む |
| SQLite 3.45.1(インデックスなし) | 0.040 秒 | 0.037 秒 | どちらも全行を読む |
数値は今回のコンテナでの各1回の計測値で、環境やデータによって変わります。今回の計測では、MySQL 8.0.46では書く場所で約70倍の差が出て、PostgreSQLとSQLiteでは差が出ませんでした。 どのDBでも同じ速さを得るには、次の書き分けにしておくのが確実です。
| 条件の種類 | 書く場所 | 例 |
|---|---|---|
| 集計関数を使わない条件 | WHERE | WHERE cat < 10、WHERE status = 'done' |
| 集計関数を使う条件 | HAVING | HAVING COUNT(*) >= 3、HAVING SUM(amount) >= 1000 |
よくあるつまずきと直し方
| 症状・エラー文 | 原因 | 直し方 |
|---|---|---|
Invalid use of group function(MySQL)/aggregate functions are not allowed in WHERE(PostgreSQL)/misuse of aggregate(SQLite) |
WHEREに集計関数を書いた | 条件をHAVINGに移す |
column "cnt" does not exist(PostgreSQL) |
HAVINGでSELECTの別名を使った | HAVING COUNT(*) >= 3 と式で書く、またはWITH句で包む |
Unknown column 'amount' in 'having clause'(MySQL)/must appear in the GROUP BY clause ...(PostgreSQL) |
集計していない列をHAVINGに書いた | MAX(amount) など集計関数で書く、または条件をWHEREに移す |
Unknown column 'total' in 'where clause'(MySQL) |
WHEREでSELECTの別名を使った | 集計結果の条件はHAVINGに書く |
| 件数が想定より少ない | WHEREで行を絞ったため、数える対象が減った | 全体の件数も要るなら SUM(CASE ...) で条件付き集計にする |
| 構文エラーになる | HAVINGをGROUP BYより前、またはORDER BYより後に書いた | GROUP BY → HAVING → ORDER BY の順に書く |
| MySQLで集計が遅い | 集計関数を使わない条件をHAVINGに書いている | その条件をWHEREに移す |
MySQLで FILTER (WHERE ...) が構文エラー |
MySQL 8.0.46では使えなかった | SUM(CASE WHEN ... THEN 1 ELSE 0 END) に書き換える |
よくある質問
Q. WHEREとHAVINGは、どちらを使えばよいですか?
A. 条件に集計関数(COUNT・SUM・AVG・MAX・MINなど)が出てくるならHAVING、出てこないならWHEREです。「完了した注文だけを対象に、3件以上ある顧客」のように両方が必要なときは、WHERE status = 'done' ... HAVING COUNT(*) >= 3 と併用します。
Q. HAVINGで cnt >= 3 のようにSELECTの別名を使ってもよいですか?
A. MySQL 8.0.46とSQLite 3.45.1では動き、PostgreSQL 16.15ではエラーになりました。複数のDBで動かす可能性があるなら、HAVING COUNT(*) >= 3 と集計関数の式で書くのがおすすめです。式を繰り返したくない場合は、WITH句やサブクエリで集計結果に名前を付けて、外側のWHEREで絞ります。
Q. HAVINGはGROUP BYなしでも使えますか?
A. 使えます。その場合は表全体が1つのグループになり、SELECT COUNT(*) FROM orders HAVING COUNT(*) > 5 のように「全体の集計結果が条件を満たすときだけ1行返す」動きになります。今回3つのDBで確認しました。
Q. SELECTに書いていない集計関数をHAVINGで使えますか?
A. 使えます。SELECT customer FROM orders GROUP BY customer HAVING COUNT(DISTINCT item) >= 3 のように、表示はしないが条件には使う、という書き方ができます。
まとめ
- HAVINGは集計したあとのグループを絞る句、WHEREは集計する前の行を絞る句
- 評価の順番は FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。この順番がWHEREとHAVINGの違いをすべて説明する
- WHEREに集計関数を書くとエラーになる(MySQL:Invalid use of group function、PostgreSQL:aggregate functions are not allowed in WHERE、SQLite:misuse of aggregate)
- HAVINGでSELECTの別名が使えたのはMySQLとSQLite。PostgreSQLではエラーになるので、集計関数の式をそのまま書くと揃う
- 重複検出は
GROUP BY 列 HAVING COUNT(*) > 1、一部の行だけを条件にするならHAVING SUM(CASE WHEN ... THEN 1 ELSE 0 END) >= 1 - 条件はAND・OR・BETWEENで組み合わせられ、サブクエリとの比較もできる
- 集計関数を使わない条件はWHEREに書く。MySQL 8.0.46では、HAVINGに書いた場合と比べて約70倍の差が出た
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQL HAVINGの使い方|WHEREとの違いとCOUNT条件の絞り込み」
株式会社RJCでは、SQLの集計や絞り込みの書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。