Learn the concept
A join combines related rows. An inner join returns matching pairs; a left join retains rows from the left side even without a match. Joining a one-to-many relationship increases row count, which can accidentally inflate aggregates.
GROUP BY summarizes rows by a key. COUNT(*) counts rows, while counting a column excludes null values. Decide which business quantity you intend to count. A customer with no tickets should not disappear from a report if the report claims to include all customers.
Pagination needs a stable order. Offset-based pagination is simple but can shift as records change. Keyset pagination continues after a known ordering key and is useful for large or changing datasets. Include a unique tie-breaker when the primary sort field is not unique.
Run and inspect
rows = [("customer-a", 2), ("customer-b", 0)]
assert sum(count for _, count in rows) == 2
# SQL shape: SELECT customer_id, COUNT(*) FROM tickets GROUP BY customer_id
# Stable page shape: WHERE id > ? ORDER BY id LIMIT ?
Your exercise
Create customers and tickets tables. Report all customers including those with zero tickets, then paginate tickets by ID with no duplicates.
Check your understanding
The report handles one-to-many relationships correctly and two consecutive pages neither overlap nor omit stable records.