Aggregation and GROUP BY
COUNT, SUM, AVG and friends; HAVING versus WHERE; and why COUNT(*) differs from COUNT(col).
Aggregate functions
| Function | Result |
|---|---|
COUNT(*) | Number of rows β includes NULLs |
COUNT(col) | Non-NULL values only |
COUNT(DISTINCT col) | Unique non-NULL values |
SUM(col)/AVG(col) | NULLs ignored in the maths |
MIN/MAX | Extremes; work on dates and text too |
STRING_AGG(col, ', ') | Concatenate grouped values |
π‘
AVG(col) averages over non-NULL rows only, so it silently ignores missing data rather than treating it as zero. Decide which behaviour you actually want.Grouping
SELECT category, COUNT(*) AS n, AVG(price) AS avg_price
FROM products
WHERE active = TRUE
GROUP BY category
HAVING COUNT(*) > 5
ORDER BY n DESC;- Every non-aggregated column in
SELECTmust appear inGROUP BY. WHEREfilters rows before grouping;HAVINGfilters the resulting groups.- Grouping by an expression? Repeat it in both clauses, or use a CTE with an alias.
Window functions (a glimpse)
When you need aggregates but want to keep every underlying row, window functions are the answer β no grouping collapse required.
SELECT name, category, price,
AVG(price) OVER (PARTITION BY category) AS cat_avg,
RANK() OVER (ORDER BY price DESC) AS price_rank
FROM products;FAQ
Why COUNT(col) lower than COUNT(*)?
Aggregates ignore NULL. Use
COUNT(*) to count rows and COUNT(DISTINCT col) to count unique values.Can I use an alias in HAVING?
In most databases yes for HAVING but no for WHERE. PostgreSQL allows referencing select aliases in GROUP BY/ORDER BY only.
Related
SELECT: reading data Indexes and query speed
Last refreshed 2026-09-17.