runtime
13 / 88

lesson 13 Calculations & data quality · intermediate

Filter groups with HAVING

Filter summaries after grouping rows.

the idea

WHERE filters rows before grouping. HAVING filters groups after aggregation. Think rows → groups → group filter → output.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

orders6 columns · 12 rows
idinteger · keycustomer_idintegerproduct_idintegerquantityintegerstatustextordered_attext
101112paid2025-01-01
102231paid2025-01-02
103121paid2025-01-05
104352pending2025-01-08
105445paid2025-02-01

showing 5 of 12 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2;

Watch for: WHERE cannot filter an aggregate computed by the same query block.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Form groups first. GROUP BY customer_id builds one row per customer with an order_count. HAVING will filter these groups next.

    step-1.sql
    SELECT customer_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY customer_id
    ORDER BY customer_id;
  2. Keep only qualifying groups. HAVING runs after aggregation. It removes groups whose aggregate fails the test, unlike WHERE which filters source rows.

    step-2.sql
    SELECT customer_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY customer_id
    HAVING COUNT(*) >= 2;

challenge 1

Show customer_id and order_count for customers with at least 2 orders.

Return customer_idorder_countany row order

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.