Mode disclosure
All modes use one coherent workspace; only disclosure and guidance change. Learn mode keeps theory, concept names, full schema help, progressive hints, and solution review available.
Fan-Out, Pre-Aggregation, Self Joins, and Non-Equi Joins / write query
M20-A07 - Checkpoint - produce one row per customer with lifetime ordered and paid amounts across fan-out hidden variants
M20-A07 - Checkpoint - produce one row per customer with lifetime ordered and paid amounts across fan-out hidden variants. Detect independent one-to-many fan-out and pre-aggregate to a common grain.
- Result grain
- one customer with independently staged lifetime ordered and paid amounts
- Exact columns
- customer_id; customer_name; order_count; lifetime_ordered_amount; lifetime_paid_amount; rows_per_customer_key
SQL editor shortcuts: Ctrl or Command Enter runs the query, Ctrl or Command Shift Enter checks it, Alt H opens the next hint, Ctrl or Command slash toggles a line comment, Ctrl or Command Shift F formats the SQL, and Escape closes transient UI.
Cursor at line 1, column 1.
Scenario
Control fan-out before aggregating: diagnose child multiplication, pre-aggregate to the parent grain, use clear self-join roles, and keep range bands non-overlapping.
Fan-Out, Pre-Aggregation, Self Joins, and Non-Equi Joins / write query
One-sentence task
M20-A07 - Checkpoint - produce one row per customer with lifetime ordered and paid amounts across fan-out hidden variants. Detect independent one-to-many fan-out and pre-aggregate to a common grain.
Learn mode disclosure
Theory, concept names, full schema help, and progressive hints are available.
Structured output contract
- Result grain
- one customer with independently staged lifetime ordered and paid amounts
- Exact columns
- customer_id; customer_name; order_count; lifetime_ordered_amount; lifetime_paid_amount; rows_per_customer_key
- Source population
- Use the prompt setup plus FROM, JOIN, WHERE, and subquery predicates as the source population. Visible rows are only examples.
- Grouping
- Group only at the requested output grain: one customer with independently staged lifetime ordered and paid amounts.
- Ordering
- return order_count; return lifetime_ordered_amount; order by customer_id
- Validation
- select-only; hidden deterministic variants.
Relevant tables
Time and difficulty
- Estimated time
- 12 minutes
- Difficulty
- 4/5
Objective and concepts
Debug the requested SQL output contract for fan-out, pre-aggregation, self joins, and non-equi joins using source grain, columns, ordering, and edge-case evidence.
Glossary links
Concept material
SQL Trail treats every query as an evidence trail: identify source grain, transform rows deliberately, then compare output to a shared contract.
A passing query must handle hidden nulls, ties, boundaries, and no-match rows when the contract makes them relevant.
Syntax card
SELECT <requested_columns>
FROM <source_table>
WHERE <source_population_filter>
GROUP BY <result_grain_columns>
ORDER BY <deterministic_tie_breakers>;- <requested_columns> means the exact output columns, aliases, and order from the visible contract.
- <source_population_filter> means the row population definition, not a copied visible-row value.
- <deterministic_tie_breakers> means all ordering and tie rules needed for repeatable output.
Why this works
Items, payments, and returns are independent child facts; aggregate each to order grain before combining them.
Edge cases
Hidden variants preserve nulls, ties, duplicates, boundaries, no-match rows, and alternate row order when those risks apply.
PostgreSQL note
The local engine uses PostgreSQL-compatible syntax, including explicit NULL predicates, deterministic ORDER BY clauses, and transactional grading.
Worked example
SELECT o.order_id, COALESCE(items.ordered_amount, 0)::numeric(10,2) AS ordered_amount, COALESCE(payments.paid_amount, 0)::numeric(10,2) AS paid_amount, COALESCE(refunds.refund_amount, 0)::numeric(10,2) AS refund_amount FROM orders o LEFT JOIN (SELECT order_id, SUM(quantity * unit_price)::numeric(10,2) AS ordered_amount FROM order_items GROUP BY order_id) items ON items.order_id = o.order_id LEFT JOIN (SELECT order_id, SUM(amount) FILTER (WHERE payment_status = 'captured')::numeric(10,2) AS paid_amount FROM payments GROUP BY order_id) payments ON payments.order_id = o.order_id LEFT JOIN (SELECT oi.order_id, SUM(r.quantity * oi.unit_price)::numeric(10,2) AS refund_amount FROM returns r JOIN order_items oi ON oi.order_item_id = r.order_item_id GROUP BY oi.order_id) refunds ON refunds.order_id = o.order_id ORDER BY o.order_id;Assumptions, dialect notes, and common traps
- Duplicate policy
- Preserve duplicate facts unless the prompt explicitly asks for distinct tuples or set semantics.
- Null policy
- Preserve NULL, empty string, zero, and false as distinct values unless the contract says to display a fallback.
- Tie-breakers
- Use every ordering rule in the contract and end tied business metrics with deterministic secondary keys when needed.
- Zero-related entities
- Preserve requested zero-related, no-match, or complete-period entities and make display fallback explicit.
- Numeric tolerance
- Round only at the requested final stage; hidden checks use the contract precision rather than visible formatting luck.
PostgreSQL-compatible local checks
Queries run in a local PGlite worker with PostgreSQL-style syntax and transactional grading.
- Independent children multiply: Items and payments are joined directly and the resulting SUM looks plausible but repeats facts. Repair: Predict child-count multiplication and pre-aggregate each child collection to the parent grain.
- DISTINCT after SUM cannot repair fan-out: Repeated equal payment or item amounts are deduplicated instead of preserving real repeated facts. Repair: Stage facts at the correct grain before SUM rather than wrapping SUM inputs in DISTINCT.
- Self joins need role names: The same table is joined without clear aliases, making referred and referring customers interchangeable. Repair: Alias each table instance by its role and qualify every selected column.
- Range bands must not overlap: A boundary amount can match two bands or no band because both endpoints are inclusive. Repair: Use lower-inclusive and upper-exclusive predicates for adjacent ranges.
Opened hints
No hints opened yet.