PostgreSQL Error 42803: Grouping Error Explained
PostgreSQL error code 42803, grouping error, occurs when a column referenced in a SELECT, HAVING, or ORDER BY clause is neither wrapped in an aggregate function nor listed in the GROUP BY clause. PostgreSQL strictly enforces SQL standard grouping rules, unlike MySQL's lenient default behavior. This error is extremely common when writing aggregation queries, but it is straightforward to fix once you understand the root cause.
Top 3 Causes and Fixes
1. Column in SELECT not included in GROUP BY
The most frequent cause. Any column in the SELECT list that isn't aggregated must appear in GROUP BY.
Broken:
-- Error: employee_name is not in GROUP BY
SELECT department_id, employee_name, AVG(salary)
FROM employees
GROUP BY department_id;
Fixed — Add the column to GROUP BY:
SELECT department_id, employee_name, AVG(salary)
FROM employees
GROUP BY department_id, employee_name;
Fixed — Use a subquery to keep both detail and aggregate:
SELECT e.employee_name, e.department_id, d.avg_salary
FROM employees e
JOIN (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
) d ON e.department_id = d.department_id;
2. Non-aggregated column used directly in HAVING
HAVING filters groups after aggregation. Using a raw column that isn't in GROUP BY inside HAVING triggers error 42803.
Broken:
-- Error: employee_name not in GROUP BY, used raw in HAVING
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING employee_name = 'Alice';
Fixed — Move the filter to WHERE:
-- WHERE filters rows BEFORE grouping
SELECT department_id, AVG(salary)
FROM employees
WHERE employee_name = 'Alice'
GROUP BY department_id;
Correct HAVING usage (filter on aggregated result):
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 60000;
3. Missing GROUP BY column inside a CTE or subquery
When building complex queries with CTEs, it's easy to forget a column in the GROUP BY clause of an inner query.
Broken:
-- Error: join_date selected but not in GROUP BY
WITH dept_summary AS (
SELECT department_id, join_date, COUNT(*) AS headcount
FROM employees
GROUP BY department_id
)
SELECT * FROM dept_summary;
Fixed — Include the column in GROUP BY or aggregate it:
WITH dept_summary AS (
SELECT department_id,
COUNT(*) AS headcount,
MIN(join_date) AS earliest_hire
FROM employees
GROUP BY department_id
)
SELECT * FROM dept_summary;
Quick Prevention Tips
Sync SELECT and GROUP BY as you write. Every non-aggregated column in your
SELECTlist must also appear inGROUP BY. A simple mental check before running the query saves debugging time.Break complex aggregations into CTEs. Splitting multi-level aggregations into named CTE steps lets you validate each grouping independently and makes the logic far easier to review and maintain.
-- Clean CTE pattern: separate aggregation steps
WITH monthly_sales AS (
SELECT salesperson_id,
DATE_TRUNC('month', sale_date) AS sale_month,
SUM(amount) AS monthly_total
FROM sales
GROUP BY salesperson_id, DATE_TRUNC('month', sale_date)
),
ranked AS (
SELECT *,
RANK() OVER (PARTITION BY sale_month ORDER BY monthly_total DESC) AS rnk
FROM monthly_sales
)
SELECT * FROM ranked WHERE rnk = 1;
Related Errors
-
42P10 — invalid_column_reference: Triggered by referencing a column in
ORDER BYthat doesn't appear in theSELECTlist under certain grouping contexts. - MySQL migration note: MySQL's default mode silently picks arbitrary values for non-aggregated columns. Migrating such queries to PostgreSQL will surface 42803 errors that need to be explicitly resolved.
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)