ORA-00979: not a GROUP BY expression は、Oracleで GROUP BY を使ったSQLに、グループ化されていない列や式を SELECT、ORDER BY、HAVING などへ書いたときに発生するエラーです。新しいエラーメッセージでは、expression: must appear in the GROUP BY clause or be used in an aggregate function のように、不足している式が示されることもあります。
原因は単純で、GROUP BY 後の結果は「グループごとに1行」になります。そのため、グループ内に複数の値が存在する可能性がある列をそのまま表示しようとすると、Oracleはどの値を代表として出せばよいか判断できません。
ORA-00979は、集計していない列・式を
GROUP BY に追加するか、SUM、COUNT、MIN、MAX、AVG などの集計関数で包むと直ります。行明細も残したい場合は、通常の GROUP BY ではなく分析関数 OVER(PARTITION BY ...) やサブクエリに分ける方法を検討します。ORA-00979とは
Oracle公式の説明では、ORA-00979は、指定した式が GROUP BY 句にも集計関数にも定数にも含まれていないのに、GROUP BY の後に処理される句で参照された場合に発生します。対象になりやすいのは SELECT 句、ORDER BY 句、HAVING 句です。
つまり、SQL構文の形だけでなく「グループ化した後に、その列が1つの値として決まるか」が問題です。グループごとに1つに決まらない列は、グループ化対象にするか、集計して1つの値にする必要があります。
| 書いている場所 | よくある原因 | 直し方 |
|---|---|---|
SELECT |
集計していない列を表示している | GROUP BY に入れるか集計する |
ORDER BY |
並び替え列がグループ化結果にない | 集計後に存在する列・式で並べる |
HAVING |
集計後条件に非集計列を書いている | WHERE へ移すか集計条件にする |
| 式・関数 | TRUNC(date,'MM') と date を混同している |
SELECT と GROUP BY の式を一致させる |
基本ルール:SELECTの非集計列はGROUP BYに入れる
もっとも多いのは、SELECT 句にある列を GROUP BY に入れ忘れるケースです。次のSQLでは、部署ごとの売上合計を出したいのに、employee_name も表示しようとしているためORA-00979になります。
-- NG: employee_name が GROUP BY にも集計関数にも含まれていない
SELECT department_id,
employee_name,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id;
部署ごと、社員ごとの売上合計を出したいなら、employee_name もグループ化します。
-- OK: 表示する非集計列を GROUP BY に入れる
SELECT department_id,
employee_name,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id, employee_name;
一方、部署ごとの集計だけが欲しいなら、社員名を表示しないのが正しい修正です。GROUP BYの基本は SQLのGROUP BYで件数をカウントする方法 も参考になります。
GROUP BYに列を追加すると集計粒度が変わる
ORA-00979を直すために、エラーになった列をすべて GROUP BY に追加すればSQLは通ることがあります。ただし、それが正しい集計とは限りません。GROUP BY に列を追加すると、集計の単位が細かくなり、行数や合計の見え方が変わります。
-- 部署ごとの売上合計: 部署ごとに1行
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id;
-- 部署 + 社員ごとの売上合計: 社員ごとに行が分かれる
SELECT department_id,
employee_name,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id, employee_name;
エラーを消すだけなら後者でも通りますが、欲しかった結果が「部署ごとに1行」なら間違った修正です。列を GROUP BY に追加する前に、集計粒度を「部署単位」「社員単位」「月単位」のように言葉で確認します。
どの列が原因かを見つける手順
長いSQLでは、どの列や式がORA-00979の原因か見つけにくいことがあります。Oracleの新しいエラーメッセージでは不足している式が表示されることがありますが、古い環境では単に not a GROUP BY expression とだけ出ることもあります。
| 見る順番 | 確認内容 | 例 |
|---|---|---|
| 1 | SELECT 句の列 |
employee_name が集計もGROUP BYもされていない |
| 2 | ORDER BY 句の列 |
order_date で並べているが集計後に存在しない |
| 3 | HAVING 句の条件 |
行単位の status や order_date をHAVINGに書いている |
| 4 | 式の一致 | TRUNC(order_date,'MM') と order_date がずれている |
| 5 | CASE式 | SELECTとGROUP BYでCASE式が一致していない |
調査時は、まず集計関数以外の SELECT 項目をすべて書き出し、GROUP BY に同じ列・同じ式があるか確認します。次に ORDER BY と HAVING を見ます。SELECT句だけ直しても、ORDER BYやHAVINGに原因が残るとエラーは消えません。
集計関数で1つの値にする
グループ内の代表値を出したい場合は、列をそのまま表示するのではなく集計関数を使います。たとえば部署ごとの最新注文日や最大金額を出すなら、MAX や MIN を使います。
-- OK: グループ内の値を集計関数で1つにする
SELECT department_id,
MAX(order_date) AS latest_order_date,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id;
合計なら SUM関数、平均なら AVG関数、件数なら COUNT関数 の使い分けを確認してください。文字列をグループ内で結合したい場合は OracleのLISTAGG関数 が候補になります。
SELECT * と GROUP BY は基本的に相性が悪い
SELECT * のまま一部の列だけで GROUP BY しようとすると、ほぼ確実にORA-00979になります。* は全列を表示するため、GROUP BYに含まれていない列までSELECT対象になるからです。
-- NG: * に含まれる列の多くが GROUP BY に入っていない
SELECT *
FROM sales
GROUP BY department_id;
-- OK: 必要な列と集計結果だけを書く
SELECT department_id,
COUNT(*) AS sales_count
FROM sales
GROUP BY department_id;
ORDER BYでORA-00979になるケース
SELECT 句を直しても、ORDER BY にグループ化されていない列が残っているとORA-00979になります。Oracle公式の説明でも、ORDER BY は GROUP BY 後に処理される句として挙げられています。
-- NG: order_date は GROUP BY 後の結果に存在しない
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id
ORDER BY order_date;
-- OK: 集計結果として存在する列で並べる
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id
ORDER BY total_amount DESC;
注文日の新しい部署順にしたいなら、MAX(order_date) のように集計後の値を作ってから並べます。
SELECT department_id,
SUM(amount) AS total_amount,
MAX(order_date) AS latest_order_date
FROM sales
GROUP BY department_id
ORDER BY latest_order_date DESC;
HAVINGでORA-00979になるケース
HAVING は集計後のグループに対する条件です。そのため、集計前の行単位の列をそのまま書くとORA-00979になります。行を絞る条件は WHERE、集計結果を絞る条件は HAVING に分けます。
-- NG: order_date は GROUP BY 後のグループ値として決まらない
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id
HAVING order_date >= DATE '2026-01-01';
-- OK: 行単位の条件は WHERE に書く
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
WHERE order_date >= DATE '2026-01-01'
GROUP BY department_id;
集計後の合計金額で絞るなら HAVING SUM(amount) >= 100000 のように集計関数を使います。HAVINGの使い分けは OracleのHAVING句の使い方 にまとめています。
式が一致していないケース
列そのものではなく、式や関数を使っている場合も注意が必要です。SELECT で TRUNC(order_date, 'MM') を表示するなら、GROUP BY にも同じ式を指定します。
-- NG: SELECT は月単位の式だが、GROUP BY は元の日付列
SELECT TRUNC(order_date, 'MM') AS order_month,
SUM(amount) AS total_amount
FROM sales
GROUP BY order_date;
-- OK: SELECT と GROUP BY の式を合わせる
SELECT TRUNC(order_date, 'MM') AS order_month,
SUM(amount) AS total_amount
FROM sales
GROUP BY TRUNC(order_date, 'MM');
月別、年別、カテゴリ別などの集計では、SELECT 句に書いた式と GROUP BY 句の式がずれていないか確認します。CASE式で分類して集計する場合も同じです。CASEを使った条件集計は OracleのCASE式 が参考になります。
CASE式で発生するケース
CASE式で分類名を作っている場合、そのCASE式全体を GROUP BY に入れる必要があります。元の列だけをGROUP BYに入れる、またはCASE式の一部だけを入れると意図しないエラーや細かすぎる集計になります。
-- OK: SELECT の CASE 式と GROUP BY の CASE 式を一致させる
SELECT CASE
WHEN amount >= 100000 THEN 'HIGH'
WHEN amount >= 50000 THEN 'MIDDLE'
ELSE 'LOW'
END AS amount_rank,
COUNT(*) AS sales_count
FROM sales
GROUP BY CASE
WHEN amount >= 100000 THEN 'HIGH'
WHEN amount >= 50000 THEN 'MIDDLE'
ELSE 'LOW'
END;
DISTINCTを付けても解決しない
ORA-00979が出たときに DISTINCT を付ければ重複が消えて直る、と考えるのは危険です。DISTINCT は結果行の重複を除くためのもので、GROUP BY後に参照できない列を参照できるようにする機能ではありません。
-- NG: DISTINCT を付けても employee_name は GROUP BY 後に決まらない
SELECT DISTINCT department_id,
employee_name,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id;
重複排除が目的なら DISTINCTの使い方、集計が目的ならGROUP BYと集計関数、というように役割を分けて考えます。
明細行も集計値も表示したい場合
「社員ごとの明細行は残したいが、部署合計も同時に出したい」という場合、通常のGROUP BYだけで解こうとするとORA-00979に寄りやすくなります。この場合は、分析関数を使うと行数を減らさずに集計値を付けられます。
-- 明細行を残しながら部署ごとの合計を表示する
SELECT employee_id,
employee_name,
department_id,
amount,
SUM(amount) OVER (PARTITION BY department_id) AS department_total
FROM sales;
GROUP BYは行をグループ単位にまとめる処理、分析関数は行を残したまま集計値を付ける処理です。行明細が必要なのか、グループごとに1行でよいのかを先に決めると、修正方針が決まります。
サブクエリに分けると直しやすいケース
集計結果とマスタ情報を一緒に表示したい場合は、先にサブクエリで集計し、あとからマスタ表と結合すると読みやすくなります。GROUP BYの対象を最小限にできるため、ORA-00979も避けやすくなります。
WITH sales_summary AS (
SELECT department_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY department_id
)
SELECT d.department_id,
d.department_name,
s.total_amount
FROM departments d
JOIN sales_summary s
ON s.department_id = d.department_id
ORDER BY s.total_amount DESC;
ORA-00937との違い
似たエラーに ORA-00937: not a single-group group function があります。どちらも集計関数まわりのエラーですが、見ているポイントが少し違います。
| エラー | よくある原因 | 例 |
|---|---|---|
ORA-00979 |
GROUP BY はあるが、SELECT/ORDER BY/HAVINGに未グループ化の列がある |
SELECT dept, name, SUM(amount) ... GROUP BY dept |
ORA-00937 |
集計関数と非集計列を混ぜているのにGROUP BYがない | SELECT dept, SUM(amount) FROM sales |
GROUP BYがあるならORA-00979、GROUP BY自体が足りないならORA-00937、と切り分けると調査しやすいです。
修正チェックリスト
| 手順 | 確認すること | 判断 |
|---|---|---|
| 1 | SELECT 句を見る |
集計関数で包まれていない列・式があるか |
| 2 | GROUP BY 句を見る |
非集計列・式がすべて含まれているか |
| 3 | ORDER BY 句を見る |
集計後に存在しない列で並べていないか |
| 4 | HAVING 句を見る |
行単位条件をHAVINGに書いていないか |
| 5 | 式やCASEを確認する | SELECTとGROUP BYの式が一致しているか |
| 6 | 行明細が必要か確認する | 必要なら分析関数やサブクエリに分ける |
| 7 | DISTINCTでごまかしていないか確認する | 重複排除と集計の役割を分ける |
よくある質問
GROUP BYに主キーを入れれば他の列も表示できますか?
Oracleでは、基本的にSELECT句の非集計列・式をGROUP BYに明示します。主キーから他列が一意に決まるように見えても、互換性と読みやすさのため、表示する非集計列はGROUP BYに入れる方針が安全です。
ORDER BYの列もGROUP BYに必要ですか?
ORDER BYで参照する列や式が、集計後の結果として存在する必要があります。GROUP BYに入れるか、MAX(order_date) のように集計結果として作ってから並べます。
HAVINGとWHEREのどちらに条件を書けばよいですか?
行を集計前に絞るならWHERE、集計後の合計や件数で絞るならHAVINGです。日付やステータスなど行単位の条件をHAVINGに書くとORA-00979の原因になりやすいです。
SELECT句の別名をGROUP BYに使えますか?
Oracleのバージョンや設定によって扱いが変わる可能性があるため、互換性を重視するなら別名ではなく元の式をGROUP BYに書くのが無難です。特に既存環境や複数バージョンをまたぐSQLでは、式を明示しておくと安全です。
明細と合計を同じSQLで出したい場合はどうしますか?
行数を減らしたくないなら、SUM(...) OVER (PARTITION BY ...) のような分析関数を使います。GROUP BYはグループごとに行をまとめるため、明細列をそのまま残す用途には向きません。
まとめ
ORA-00979は、GROUP BY後の結果に存在しない列や式を、SELECT、ORDER BY、HAVINGなどで参照したときに発生します。まずSELECT句の非集計列を確認し、GROUP BYに入れるのか、集計関数で1つの値にするのかを決めます。
ORDER BYやHAVINGにも同じルールが効きます。行単位の条件はWHEREへ、集計後の条件はHAVINGへ、並び替えは集計後に存在する列や式へ寄せます。明細行も必要なら、GROUP BYではなく分析関数やサブクエリに分けると、エラーを避けつつ読みやすいSQLになります。
