Skip to content
PostgreSQL

PostgreSQL Error 42803

column must appear in the GROUP BY clause or be used in an aggregate function

Applies to: PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last technically reviewed 24 October 2026 on PostgreSQL 18.6 (PGlite 0.5.11)
The full message
ERROR:  column "emp.name" must appear in the GROUP BY clause or be used in an aggregate function
Other forms of this message
Aggregate in WHERE (use HAVING)
ERROR:  aggregate functions are not allowed in WHERE
Aggregate in an UPDATE SET clause
ERROR:  aggregate functions are not allowed in UPDATE
An aggregate inside another aggregate
ERROR:  aggregate function calls cannot be nested
SQLSTATE
42803
Condition name
grouping_error
Product
PostgreSQL
Versions
PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last reviewed
24 October 2026

What it means

SQLSTATE 42803 means a query mixes grouped rows with a column that is neither grouped nor aggregated, or uses an aggregate function in a place where aggregates are not allowed.

With GROUP BY, or with an aggregate such as count() or sum() and no GROUP BY, PostgreSQL returns one row per group. Every column in SELECT, HAVING and ORDER BY must then have a single value per group: either it is in the GROUP BY list, or it is inside an aggregate function. A plain column that can have several values in one group (several employee names in one department) has no single answer, so PostgreSQL raises 42803 (grouping_error) and names the column as table.column.

There is one exception: when you group by a table's primary key, PostgreSQL knows the table's other columns are functionally dependent on it and lets you select them without listing them. MySQL (with ONLY_FULL_GROUP_BY off) and older tools allow non-grouped columns and pick an arbitrary value; PostgreSQL never does. The same SQLSTATE covers an aggregate in WHERE (which is evaluated before grouping, so the condition belongs in HAVING), an aggregate in UPDATE ... SET, and one aggregate nested inside another.

Common causes

  • SELECT lists a column that is not in GROUP BY, for example a name next to count(*) per department; common in queries ported from MySQL.
  • SELECT mixes an aggregate with a plain column and has no GROUP BY at all, such as SELECT name, sum(salary) FROM emp.
  • ORDER BY or HAVING refers to a column that is not grouped.
  • A condition on an aggregate is written in WHERE instead of HAVING.
  • The query tries to take the average of a count (avg(count(*))) in one step, which needs a subquery.

Diagnose it

Read-only These queries only read the system catalogs (pg_catalog, information_schema) and statistics views such as pg_stat_activity and pg_locks. They do not change data, roles or settings, so they are safe to run first.

Does the column have more than one value per group?

Count the distinct values of the column named in the message within each group.

SELECT dept, count(DISTINCT name) AS names_in_group
FROM emp
GROUP BY dept
HAVING count(DISTINCT name) > 1;
What to look for

Groups where the column has several values. For these you must decide what you want: all of them (string_agg), one of them (min, max, or the top row per group) or a finer grouping.

Is the grouped column the primary key?

Grouping by the primary key lets you select the table's other columns without listing them.

SELECT conname, pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'emp'::regclass
  AND contype = 'p';
What to look for

The primary key columns, for example PRIMARY KEY (emp_id). A unique index or UNIQUE constraint does not count for this rule.

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.

Aggregate the column, or add it to GROUP BY

Read-only

If you want one row per department, aggregate the extra column (string_agg, min or max). If you want one row per department and name, add the column to GROUP BY; that changes the result to finer groups.

SELECT dept,
       string_agg(name, ', ' ORDER BY name) AS names,
       count(*) AS employees
FROM emp
GROUP BY dept;

Group by the primary key to select its other columns

Read-only

When the grouping is really per row of a table (for example per customer when joining orders), group by that table's primary key and select its other columns freely.

SELECT c.id, c.name, c.email, count(o.id) AS orders
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id;

Get the top row per group with DISTINCT ON or a window function

Read-only

A common reason for the error is wanting "the employee with the highest salary in each department". GROUP BY cannot return a whole row; DISTINCT ON (PostgreSQL) or row_number() can.

SELECT DISTINCT ON (dept) dept, name, salary
FROM emp
ORDER BY dept, salary DESC;

Move aggregate conditions to HAVING, and nest aggregates in a subquery

Read-only

WHERE filters rows before grouping; HAVING filters groups after. To aggregate an aggregate, compute the inner one in a subquery.

SELECT dept, sum(salary) AS total
FROM emp
GROUP BY dept
HAVING sum(salary) > 5000;

SELECT avg(employees) AS avg_per_dept
FROM (SELECT dept, count(*) AS employees FROM emp GROUP BY dept) AS d;

Examples

A non-grouped column next to count(*)

CREATE TABLE emp (emp_id int PRIMARY KEY, name text, dept text, salary numeric(10,2));
INSERT INTO emp VALUES (1, 'Sara', 'Sales', 5000), (2, 'Ali', 'Sales', 4000), (3, 'Omar', 'IT', 6000);

SELECT dept, name, count(*)
FROM emp
GROUP BY dept;
Result
ERROR:  column "emp.name" must appear in the GROUP BY clause or be used in an aggregate function

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

An aggregate in WHERE

CREATE TABLE emp (emp_id int PRIMARY KEY, name text, dept text, salary numeric(10,2));

SELECT dept
FROM emp
WHERE sum(salary) > 5000
GROUP BY dept;
Result
ERROR:  aggregate functions are not allowed in WHERE

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

ORDER BY a column that is not grouped

CREATE TABLE emp (emp_id int PRIMARY KEY, name text, dept text, salary numeric(10,2));

SELECT dept, count(*)
FROM emp
GROUP BY dept
ORDER BY name;
Result
ERROR:  column "emp.name" must appear in the GROUP BY clause or be used in an aggregate function

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

Average of a count in one step

CREATE TABLE emp (emp_id int PRIMARY KEY, name text, dept text, salary numeric(10,2));

SELECT avg(count(*)) FROM emp GROUP BY dept;
Result
ERROR:  aggregate function calls cannot be nested

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

How to prevent it

Before writing a grouped query, decide what one output row represents (a department, a customer, a month) and make every other column either a grouping column or an aggregate. For "the whole row with the highest value per group", reach for DISTINCT ON or a window function rather than GROUP BY.

When moving queries from MySQL, test them on PostgreSQL early, or enable ONLY_FULL_GROUP_BY on MySQL so the same mistakes are rejected there too.

Common questions

How do I fix "must appear in the GROUP BY clause or be used in an aggregate function"?

Wrap the named column in an aggregate (min, max, string_agg) if you want one row per group, or add it to GROUP BY if you want finer groups. If you need the whole top row of each group, use DISTINCT ON or row_number() instead.

Why does the same query work in MySQL?

With ONLY_FULL_GROUP_BY disabled, MySQL returns an arbitrary value from the group for non-grouped columns. PostgreSQL refuses, because the result would not be well defined. With ONLY_FULL_GROUP_BY enabled, MySQL rejects the query too.

Why can I select other columns when I group by the id?

When the grouping includes a table's primary key, every other column of that table has exactly one value per group (it is functionally dependent on the key), so PostgreSQL allows it. This works for a primary key, not for a unique index.

Learn more on CodeWithSQL

Official documentation

Last technically reviewed 24 October 2026; examples and diagnostics run on PostgreSQL 18.6 (PGlite 0.5.11). SQL-level examples were run in PGlite (PostgreSQL compiled to WebAssembly); examples that need a running server are shown as documented in the PostgreSQL documentation.

Still stuck on a PostgreSQL error?

Browse the full PostgreSQL error library, or get hands-on help from an experienced SQL professional.