Medium sql postgresql

How do GROUP BY and HAVING differ, and where does WHERE fit?

The 30 second answer

They do different jobs rather than being alternatives. Group by collects rows with matching keys into groups, while having filters those groups after aggregation. Where filters individual rows before that happens. For example, I first keep paid orders, then group them by customer, and finally keep customers whose total exceeds a threshold. Moving that total condition into where would be invalid at the same query level.

GROUP BY forms groups of rows with equal grouping keys; aggregates such as SUM produce a result per group. HAVING retains or removes those groups. WHERE removes individual input rows before grouping. This is the logical meaning, not a promise about physical execution order: the optimizer can transform a query while preserving its result.

SELECT customer_id, SUM(amount) AS paid_total
FROM orders
WHERE status = 'PAID'
GROUP BY customer_id
HAVING SUM(amount) > 1000;

The input filter keeps only paid orders. The grouping combines each customer's retained orders. The final condition keeps only customer groups with paid totals above the threshold. For paid amounts 600 and 500, the group passes with 1100 even though neither individual amount exceeds 1000. Replacing the final condition with WHERE amount > 1000 therefore changes the business meaning. Writing WHERE SUM(amount) > 1000 is invalid at this query level because the aggregate result does not yet exist there.

Selected columns must be grouped, aggregated, or permitted by the database's functional-dependency rules. PostgreSQL permits selecting other columns of a table when its primary key is grouped. HAVING can also reference grouping keys, not only aggregate expressions. Put ordinary input-row filters in WHERE for clarity; do not assume manually moving conditions always improves performance.

An explicit GROUP BY is not mandatory for HAVING: without it, the query treats all selected input rows as a single group. For example, SELECT COUNT(*) FROM orders HAVING COUNT(*) > 0 returns a count row only when at least one row exists. Grouping does not guarantee result ordering; add ORDER BY when the output order matters.

Sources (3)
  1. PostgreSQL: GROUP BY and HAVING
  2. PostgreSQL: aggregate expressions
  3. PostgreSQL: SELECT