After this lesson, you will be able to: Use GROUP BY + HAVING + aggregations (COUNT, SUM, AVG, MIN, MAX) to summarize data.
GROUP BY turns row-level data into business reports. Combined with JOINs you can answer almost any 'how many,' 'total,' or 'average' question.
Functions that collapse a column to a single value.
SELECT COUNT(*) FROM users; -- total usersSELECT COUNT(deleted_at) FROM users; -- count of non-null deleted_atSELECT COUNT(DISTINCT user_id) FROM posts; -- unique users who postedSELECT SUM(amount) FROM orders;SELECT AVG(amount)::numeric(10,2) FROM orders; -- cast to 2 decimalsSELECT MIN(created_at) FROM posts;SELECT MAX(created_at) FROM posts;
Aggregate within categories.
-- Posts per userSELECT user_id, COUNT(*) AS post_countFROM postsGROUP BY user_idORDER BY post_count DESC;-- With JOIN to get user namesSELECT u.id, u.name, COUNT(p.id) AS post_countFROM users uLEFT JOIN posts p ON p.user_id = u.idGROUP BY u.id, u.nameORDER BY post_count DESC;
WHERE filters rows; HAVING filters groups.
-- Users with >= 3 published postsSELECT u.id, u.name, COUNT(p.id) AS post_countFROM users uJOIN posts p ON p.user_id = u.idWHERE p.published = TRUE -- filter before groupingGROUP BY u.id, u.nameHAVING COUNT(p.id) >= 3 -- filter on aggregate resultORDER BY post_count DESC;
Real-world report patterns.
-- Top 10 by some metricSELECT user_id, COUNT(*) AS post_countFROM postsGROUP BY user_idORDER BY post_count DESCLIMIT 10;-- Sum by month (Postgres date_trunc)SELECT date_trunc('month', created_at) AS month, SUM(amount) AS revenueFROM ordersGROUP BY monthORDER BY month;-- FILTER clause for conditional counts (cleaner than CASE)SELECT user_id,COUNT(*) FILTER (WHERE published) AS published_count,COUNT(*) FILTER (WHERE NOT published) AS draft_countFROM postsGROUP BY user_id;
Putting filters on raw rows in HAVING instead of WHERE (slower + sometimes wrong). GROUP BY missing a column → ambiguous result or error. COUNT(*) vs COUNT(col): * counts rows, col counts non-null values. Forgetting AS for aliases when used in ORDER BY/HAVING in older SQL dialects.
Sign in and purchase access to unlock this lesson.