Логотип Workflow

Article

Updated at:

Group By Having

Stage 5. GROUP BY and HAVING for Business Metrics

When business asks “What is total revenue?” or “How many orders per customer?”, plain row-level SELECT is not enough. You need aggregation: convert many rows into compact summaries. GROUP BY and HAVING are made for this.

Aggregation pipeline

What GROUP BY does

GROUP BY splits rows into groups by one or more keys. Then aggregates like COUNT, SUM, AVG, MIN, MAX are calculated per group.

Example:

SELECT user_id,
       COUNT(*) AS orders_count,
       SUM(amount) AS total_amount
FROM orders
WHERE status = 'PAID'
GROUP BY user_id;

Execution logic:

  1. Keep only rows from WHERE.
  2. Group remaining rows by user_id.
  3. Compute count and sum for each group.

What HAVING does

HAVING filters already-built groups after GROUP BY.

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

Meaning: keep only users with total paid amount at least 1000.

Key difference:

  • WHERE filters rows before grouping;
  • HAVING filters groups after grouping.

Terms and keywords in one table

KeywordRoleExample
GROUP BYGroups rows by keyGROUP BY user_id
COUNT(*)Counts rows per groupNumber of orders
SUM(amount)Sums values per groupRevenue
HAVINGFilters groups by conditionHAVING SUM(amount) > 1000

Example with output

SELECT user_id,
       COUNT(*) AS orders_count,
       SUM(amount) AS total_amount
FROM orders
WHERE status = 'PAID'
GROUP BY user_id
ORDER BY total_amount DESC;
user_idorders_counttotal_amount
154700.00
221400.00
31300.00

Common beginner mistakes

  1. Using HAVING when WHERE should be used.
  2. Selecting a non-aggregated field not listed in GROUP BY.
  3. Grouping by too granular keys (for example full timestamp) and getting tiny fragmented groups.

Practical scenario: top customers by month

SELECT user_id,
       SUM(amount) AS revenue
FROM orders
WHERE status = 'PAID'
  AND created_at >= DATE '2026-05-01'
  AND created_at <  DATE '2026-06-01'
GROUP BY user_id
HAVING SUM(amount) >= 1000
ORDER BY revenue DESC;

This is a ready baseline for monthly top-customer reporting.

Practice Before the Next Lesson

Build the query step by step:

  1. Start with row-level SELECT for paid orders.
  2. Add GROUP BY.
  3. Add HAVING.

After each step, compare output transformation: rows → groups → filtered groups.