column must appear in the GROUP BY clause
You selected a column that isn't grouped and isn't wrapped in an aggregate. Postgres, MySQL (in strict mode), and SQL Server all refuse to guess which row's value you meant.
The exact wording differs by engine, but it's the same complaint everywhere:
- PostgreSQL:
ERROR: column "orders.order_date" must appear in the GROUP BY clause or be used in an aggregate function - MySQL (with
ONLY_FULL_GROUP_BY, the default since 5.7):Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'orders.order_date' which is not functionally dependent on columns in GROUP BY clause - SQL Server:
Column 'orders.order_date' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
The cause
GROUP BY collapses many rows into one per group. Every column in SELECT
has to be either in the GROUP BY list, or wrapped in an aggregate
(COUNT, SUM, MIN, MAX, AVG, ...) so the engine knows how to reduce
many values down to one. If you select a plain column that's neither, the
engine has no rule for which of the group's rows to take it from — so it
refuses.
This is the classic version:
-- customer_id has many order_date values per group — which one wins?
SELECT customer_id, order_date, COUNT(*)
FROM orders
GROUP BY customer_id;The fix
Pick one of two honest answers:
You don't actually need that column — drop it:
Loading editor…
You need a specific row's value from the group — say so explicitly with an aggregate, instead of leaving it to chance:
Loading editor…
If you need the whole row for whichever order is most recent (not just one column from it), aggregating won't get you there — see Top N per group for the window-function pattern that does.
The SQLite trap: it won't even tell you
This is the one place SQLite is more dangerous than the strict engines, not
less. It has no ONLY_FULL_GROUP_BY-style check — it silently picks a value
from an arbitrary row in the group and moves on:
Loading editor…
That ran without a single warning, and order_date looks like a real
answer — but which row it came from isn't defined by the query, only by
whatever order SQLite happened to scan the table in. Change an index, add a
row, or upgrade SQLite, and the value can change out from under you with no
error to flag it. If you write this in SQLite and it "works," test the
same query against Postgres or MySQL before you trust it — they'll catch
what SQLite won't.
- GROUP BY — the full rules for what can and can't appear in the SELECT list.
- HAVING — filtering after grouping, the other half of this clause pair.
- Top N per group — when you need a whole row per group, not just one aggregated column.
- COUNT — the aggregate you'll reach for most often here.
orders and watch which order_date comes back.Open the playground →