問題点SUM()、COUNT()、AVG() などの計算結果に基づいてクエリ結果をフィルタリングしようとした際、PostgreSQL でエラーが発生することがあります。おそらく、次のようなクエリを書いたのではないでしょうか:
-- このクエリはエラーをスローします
SELECT user_id, COUNT(order_id)
FROM orders
WHERE COUNT(order_id) > 10
GROUP BY user_id;
実行した瞬間に、次のエラーが表示されます:
ERROR: aggregate functions are not allowed in WHERE
エラーが発生する理由SQL クエリを工場の組み立てラインのように考えてみてください。PostgreSQL はコマンドを上から順に処理するわけではありません。データの整合性を保つために、厳格な論理的順序に従います。以下が実行順序です:
- FROM / JOIN: データベースが原材料(テーブル)を収集します。- WHERE: グループ化が行われる前に、条件に一致しない個々の行を破棄します。- GROUP BY: 残りの行をグループ(バケツ)に整理します。- Aggregation (集約): それらのグループに対して計算(
SUMやCOUNTの算出)を実行します。- HAVING: 計算結果に基づいてグループをフィルタリングします。- SELECT: 最終的に、どの列を表示するかを決定します。このエラーが発生するのは、WHEREステップでグループ単位の計算(集約)を使用しようとしているためです。その時点では、データベースはまだグループを作成していません。まだ存在しない数値に基づいてフィルタリングすることはできないのです。
解決策 1:HAVING 句を使用するこのシナリオでは、HAVING 句が最適なツールです。WHERE 句と全く同じように機能しますが、グループ化が完了するのを待ってから処理を行います。
修正方法:- WHERE 句から集約関数を削除します。- GROUP BY ステートメントの直後に HAVING 句を挿入します。```
-- 正しい方法 SELECT user_id, COUNT(order_id) as total_orders FROM orders GROUP BY user_id HAVING COUNT(order_id) > 10;
## 解決策 2:共通テーブル式 (CTE) を使用する「20回の取引で合計5,000ドル以上を費やした顧客を探す」といった複雑なレポートの場合、CTE(`WITH` 句)を使用するとコードの可読性が向上します。これは本質的に、通常通りクエリを実行できる一時的なテーブルを作成するものです。
-- 可読性を高めるために CTE を使用 WITH customer_stats AS ( SELECT user_id, SUM(amount) as total_spent FROM payments GROUP BY user_id ) SELECT * FROM customer_stats WHERE total_spent > 5000;
このバージョンでは、集約処理は `customer_stats` ブロック内に収められています。最後の `SELECT` が実行される頃には、`total_spent` は標準的なデータ列として扱われるため、慣れ親しんだ `WHERE` フィルタを使用できるようになります。
## 修正を確認する方法ロジックの再確認は簡単です。クエリ結果で特定のユーザーの注文数が12件と表示された場合、そのIDに対して簡単な整合性チェックを実行します:
SELECT COUNT(*) FROM orders WHERE user_id = 742; -- 結果に表示された ID に置き換えてください
手動でのカウントが集約結果と一致すれば、クエリのロジックは正しいと言えます。
## パフォーマンス向上のためのヒント- **WHERE は行のためのもの:** 不要なデータを早い段階で除外するために使用します(例:`WHERE status = 'active'`)。これによりクエリが高速化されます。- **HAVING はグループのためのもの:** 複数の行を一度に確認する必要がある計算にのみ使用してください。- **それらを組み合わせる:** すべてを `HAVING` に入れないでください。`WHERE` で1,000,000行の「キャンセル済み」データをフィルタリングする方が、すべてをグループ化してから後でフィルタリングするよりもはるかに高速です。高パフォーマンスなクエリの例:
SELECT category, AVG(price) FROM products WHERE in_stock = true -- 行レベルのフィルタ (高速) GROUP BY category HAVING AVG(price) > 150; -- グループレベルのフィルタ

