DEV Community

umzzil nng
umzzil nng

Posted on • Originally published at oraerror.com

PostgreSQL 42803 Error: Causes and Solutions Complete Guide

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;
Enter fullscreen mode Exit fullscreen mode

Fixed — Add the column to GROUP BY:

SELECT department_id, employee_name, AVG(salary)
FROM employees
GROUP BY department_id, employee_name;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Quick Prevention Tips

  • Sync SELECT and GROUP BY as you write. Every non-aggregated column in your SELECT list must also appear in GROUP 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;
Enter fullscreen mode Exit fullscreen mode

Related Errors

  • 42P10 — invalid_column_reference: Triggered by referencing a column in ORDER BY that doesn't appear in the SELECT list 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)