- Error code
- ORA-00979
- Product
- Oracle Database
- Versions
- Oracle Database 19c, 21c, 23ai and 26ai (wording differs: see below)
- Category
- SQL Syntax & Query Structure
- Last reviewed
- 24 October 2026
On this page
What it means
ORA-00979 means the query has a GROUP BY clause, but the SELECT list, HAVING clause or ORDER BY clause uses an expression that is neither in the GROUP BY nor inside an aggregate function.
After GROUP BY, each group becomes one row, so only the grouped expressions, aggregates over the group and constants still have a single value. Oracle's current reference explains that the SELECT, HAVING and ORDER BY clauses are processed after GROUP BY: an expression that is not part of the grouped result can no longer be referenced there. The current message names the expression; 19c and 21c print only "not a GROUP BY expression", so you have to find it yourself.
The match is on the expression, not just the column: the 19c reference says the expression must match one of the expressions in the GROUP BY clause. Grouping by TRUNC(order_date) and selecting order_date fails, for example. If the query has no GROUP BY clause at all, Oracle raises ORA-00937 instead.
Common causes
- A column was added to the SELECT list of a grouped query but not to the GROUP BY clause.
- ORDER BY or HAVING uses a column that is not grouped, such as ORDER BY order_date in a query grouped by customer_id.
- The GROUP BY uses an expression (TRUNC(order_date), UPPER(name), EXTRACT(YEAR FROM ...)) but the SELECT list uses the bare column or a different expression.
- A query joins a lookup table and selects its descriptive column (for example c.name) while grouping only by the key c.customer_id.
- A scalar subquery in the SELECT list of a grouped query refers to a column of the outer query that is not grouped.
Diagnose it
Read-only These queries only read the data dictionary (USER_, ALL_ and DBA_ views) and dynamic performance views (V$). They do not change data, privileges or settings, so they are safe to run first. DBA_ and V$ views need the matching privileges.
Read the expression from the message (current releases)
In current releases the text before the colon is the expression that is missing from GROUP BY. In 19c and 21c, compare every non-aggregated expression in SELECT, HAVING and ORDER BY with the GROUP BY list, character for character. SQL*Plus marks the first one it rejects with an asterisk.
Check whether the extra column is really one value per group
If you believe the ungrouped column has only one value in each group (a customer name per customer id, say), prove it before you add it to GROUP BY.
SELECT o.customer_id, COUNT(DISTINCT c.name) AS names_per_group
FROM shop.orders o
JOIN shop.customers c ON c.customer_id = o.customer_id
GROUP BY o.customer_id
HAVING COUNT(DISTINCT c.name) > 1; No rows: the column is functionally one value per group, and adding it to GROUP BY does not change the result. Any rows returned mean the groups would split, so decide which value you want (MIN, MAX, LISTAGG) instead.
How to fix it
Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.
Add the expression to GROUP BY
Read-onlyWhen the column should define the groups, or is one value per group, list it in GROUP BY exactly as it is written in the SELECT list.
SELECT o.customer_id, c.name, COUNT(*) AS order_count
FROM shop.orders o
JOIN shop.customers c ON c.customer_id = o.customer_id
GROUP BY o.customer_id, c.name;
Wrap it in an aggregate to choose one value per group
Read-onlyWhen a group has many values and you want one of them, say which: the latest date, the first status alphabetically, or all of them joined as a list.
SELECT customer_id,
COUNT(*) AS order_count,
MAX(order_date) AS last_order,
LISTAGG(status, ', ') WITHIN GROUP (ORDER BY status) AS statuses
FROM shop.orders
GROUP BY customer_id;
Use the same expression in SELECT, HAVING and ORDER BY as in GROUP BY
Read-onlyWhen you group by a calculated value, repeat that calculation wherever you use it (or group in a subquery and give it an alias there).
SELECT TRUNC(order_date, 'MM') AS order_month, COUNT(*) AS order_count
FROM shop.orders
GROUP BY TRUNC(order_date, 'MM')
ORDER BY TRUNC(order_date, 'MM');
Order by an aggregate or a grouped column only
Read-onlyORDER BY is processed after GROUP BY, so sort by something the grouped rows still have, such as the count or the latest date.
SELECT customer_id, COUNT(*) AS order_count
FROM shop.orders
GROUP BY customer_id
ORDER BY MAX(order_date) DESC;
Examples
Selecting a column that is not grouped
Not reproduced here: no Oracle Database is installed for this site. The result follows the message format in Oracle's current error reference with this example's column filled in; how the expression is printed (case, quoting, table alias) is illustrative. Oracle Database 19c and 21c print "ORA-00979: not a GROUP BY expression".
SELECT customer_id, status, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;
ORA-00979: STATUS: must appear in the GROUP BY clause or be used in an aggregate function
Grouping by a truncated date but selecting the raw date
Not reproduced here: no Oracle Database is installed for this site. The message format comes from Oracle's current error reference; the printed expression is illustrative, and 19c and 21c show "not a GROUP BY expression". Selecting TRUNC(order_date) instead matches the GROUP BY expression.
SELECT order_date, COUNT(*) AS order_count
FROM orders
GROUP BY TRUNC(order_date);
ORA-00979: ORDER_DATE: must appear in the GROUP BY clause or be used in an aggregate function
ORDER BY a column that the groups no longer have
Not reproduced here: no Oracle Database is installed for this site. The message format comes from Oracle's current error reference, which lists ORDER BY among the clauses processed after GROUP BY; the printed expression is illustrative. Use ORDER BY MAX(order_date) or another aggregate.
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
ORDER BY order_date;
ORA-00979: ORDER_DATE: must appear in the GROUP BY clause or be used in an aggregate function
How to prevent it
Before running a grouped query, check each non-aggregated expression in SELECT, HAVING and ORDER BY against the GROUP BY list, written the same way. When the grouping key is a calculation, group in a subquery or a WITH clause, give it an alias there, and use the alias in the outer query.
Group by the key and pick descriptive columns deliberately: either group by them too (after checking they are one value per key) or wrap them in MIN, MAX or LISTAGG. See ORDER BY, GROUP BY and HAVING explained.
Common questions
How do I fix ORA-00979 not a GROUP BY expression?
Find the expression in SELECT, HAVING or ORDER BY that is not grouped (current releases name it in the message), then either add it to GROUP BY exactly as written or wrap it in an aggregate such as MAX, MIN or LISTAGG.
Why does it fail when I group by TRUNC(order_date) and select order_date?
Oracle compares expressions, not just column names. order_date has many values inside one TRUNC(order_date) group, so it is not part of the grouped result. Select TRUNC(order_date) as well, or wrap order_date in MIN or MAX.
How is ORA-00979 different from ORA-00937?
ORA-00979 needs a GROUP BY clause that is missing one of the expressions you use. ORA-00937 is raised when there is no GROUP BY at all and a plain column sits next to a group function.
Learn more on CodeWithSQL
Official documentation
- ORA-00979 (Oracle Database Error Messages)
- SELECT, including the GROUP BY clause (Oracle SQL Language Reference 19c)
- LISTAGG (Oracle SQL Language Reference 19c)
Last technically reviewed 24 October 2026; examples and diagnostics run on Oracle Database Error Messages reference (26ai, 21c and 19c); not run locally. Messages and explanations follow Oracle's Database Error Messages reference; no Oracle Database was available, so the examples were not run locally.