Lesson 8 of 8 · 55 min

Capstone: live SQL + modeling mock

Integrated marketplace mock: monthly GMV/buyers, first vs repeat, top category windows, refunds schema, warehouse grains, and a six-axis hire rubric.

Lesson 8 · Capstone

Live SQL + modeling mock (hire bar)

Integrate the track under a clock

This lesson is a full mock: a product analytics SQL case, a short schema design, and a warehouse grain defense. You will see a worked senior solution, a scoring rubric, and self-check drills. Treat it like a real 45-60 minute loop - narrate, do not silent-type.
Capstone prompt (use this in mocks): Marketplace 'Swap - buyers and sellers, listings, orders, order_items, payments, events. Questions: (A) monthly GMV and buyers, (B) first-time vs repeat buyers, (C) top category per buyer last 90 days, (D) schema for refunds partial/full, (E) warehouse fact grain for finance vs product analytics.

Time box (45-60 min mock)

code
10-5   min  clarify definitions (GMV, buyer, timezone, refunds)25-20  min  SQL A+B with CTEs; dry-run grain320-30 min  SQL C windows; edge cases430-40 min  schema D refunds + constraints540-50 min  warehouse E facts/dims + SCD note650-60 min  EXPLAIN/index optional; recap assumptions78If 45 min: drop EXPLAIN; keep A-D solid.

Definitions to lock before coding

GMV: sum of paid order item net amounts (ex-tax) in USD; exclude cancelled; subtract full refunds in the month they refund (state this). Buyer: user_id with ≥1 paid order. Month: UTC calendar month of order paid_at. Say each aloud.

Worked SQL A - monthly GMV and buyers

sql
1WITH paid_items AS (2  SELECT3    i.order_item_id,4    o.order_id,5    o.buyer_id,6    DATE_TRUNC('month', o.paid_at AT TIME ZONE 'UTC') AS month,7    i.net_cents8  FROM order_items i9  JOIN orders o ON o.order_id = i.order_id10  WHERE o.status IN ('paid', 'fulfilled', 'refunded_partial')11    AND o.paid_at IS NOT NULL12),13monthly AS (14  SELECT15    month,16    SUM(net_cents) AS gmv_cents,17    COUNT(DISTINCT buyer_id) AS buyers18  FROM paid_items19  GROUP BY 120)21SELECT * FROM monthly ORDER BY month;
Note: if refunds are separate rows in a refunds table, left join aggregated refunds by order_item and subtract. Do not join refund lines into items without aggregating first.

Worked SQL B - first-time vs repeat buyers per month

sql
1WITH first_paid AS (2  SELECT buyer_id, MIN(paid_at) AS first_paid_at3  FROM orders4  WHERE paid_at IS NOT NULL5  GROUP BY 16),7orders_tagged AS (8  SELECT9    o.order_id,10    o.buyer_id,11    DATE_TRUNC('month', o.paid_at) AS month,12    CASE13      WHEN DATE_TRUNC('month', o.paid_at)14         = DATE_TRUNC('month', f.first_paid_at)15      THEN 'first_time'16      ELSE 'repeat'17    END AS buyer_type_for_order18  FROM orders o19  JOIN first_paid f ON f.buyer_id = o.buyer_id20  WHERE o.paid_at IS NOT NULL21)22SELECT23  month,24  buyer_type_for_order,25  COUNT(DISTINCT buyer_id) AS buyers,26  COUNT(*) AS orders27FROM orders_tagged28GROUP BY 1, 229ORDER BY 1, 2;

Worked SQL C - top category per buyer (90d)

sql
1WITH spend AS (2  SELECT3    o.buyer_id,4    p.category_id,5    SUM(i.net_cents) AS spend_cents6  FROM orders o7  JOIN order_items i ON i.order_id = o.order_id8  JOIN products p ON p.product_id = i.product_id9  WHERE o.paid_at >= NOW() - INTERVAL '90 days'10  GROUP BY 1, 211),12ranked AS (13  SELECT *,14    ROW_NUMBER() OVER (15      PARTITION BY buyer_id16      ORDER BY spend_cents DESC, category_id  -- stable tie-break17    ) AS rn18  FROM spend19)20SELECT buyer_id, category_id, spend_cents21FROM ranked22WHERE rn = 1;

Worked schema D - refunds

sql
1CREATE TABLE refunds (2  refund_id      BIGSERIAL PRIMARY KEY,3  order_id       BIGINT NOT NULL REFERENCES orders(order_id),4  order_item_id  BIGINT REFERENCES order_items(order_item_id), -- null = whole-order refund5  amount_cents   BIGINT NOT NULL CHECK (amount_cents > 0),6  reason_code    TEXT,7  status         TEXT NOT NULL CHECK (status IN ('pending','succeeded','failed')),8  created_at     TIMESTAMPTZ NOT NULL DEFAULT now()9);10-- App + periodic job: sum(succeeded refunds) <= order/item net11-- Partial unique not required; allow multiple partial refunds12CREATE INDEX refunds_order_idx ON refunds (order_id, created_at);
Discuss concurrency: two refund requests racing - use transactions and remaining-refundable checks. Finance may want immutable ledger entries instead of mutable status; mention both altitudes.

Worked warehouse E - dual grains

sql
1-- Finance mart grain: one row per order_item per day of recognition (simplified)2fact_finance_item_recognized (...)34-- Product analytics grain: one row per paid order_item (event time)5fact_product_order_items (6  order_item_id, paid_date_key, buyer_key, product_key,7  net_cents, qty8)910-- Refunds as separate fact at refund line grain (not negative items silently)11fact_refunds (refund_id, refund_date_key, order_item_key, amount_cents)1213-- GMV net of refunds = sum items - sum refunds with documented timing rules14-- dim_buyer SCD2 for segment; facts store buyer_key version at purchase

Hire rubric (self-score 1-4 each)

code
1Axis                 4 (strong)                         1 (no hire)2-------------------  ---------------------------------  ------------------------3Definitions          locks GMV/buyer/time before SQL    jumps into joins4Grain control        CTEs named; no fan-out             SUM after item fan-out5SQL correctness      windows/joins null-safe            NOT IN / WHERE on LEFT6Schema judgment      constraints + refund race          floats, no keys7Warehouse sense      fact grain + SCD note              OLTP copy-paste8Communication        narrates checks aloud              silent coding910Hire bar: ≥3 on all axes. One axis at 1 → reject risk.

Practice variants (rotate these)

  1. 01Variant: cohort retention SQL - first order month × active months.
  2. 02Variant: sessionize clickstream 30m gaps then conversion rate by session.
  3. 03Variant: SCD2 point-in-time revenue by segment.
  4. 04Variant: detect double-charged payments with self-join or window.
  5. 05Variant: multi-tenant schema + RLS story.
  6. 06Variant: EXPLAIN a slow dashboard query; propose composite index.
  7. 07Variant: funnel 3 steps with 7-day windows per step.
  8. 08Variant: rolling 28d unique actives with careful distinct strategy.
  9. 09Variant: partial unique soft-delete email redesign.
  10. 10Variant: allocate order-level discount across lines by revenue weight.
After each mock, rewrite only your weakest axis answer. Volume of new prompts matters less than killing one recurring grain bug. Keep a personal 'trap list' from L1-L7.
The hire packet quote interviewers write is often: 'Clear grain, safe joins, defined metrics.' Make that sentence easy to write about you.

Final integrated answer skeleton (speak this)

1) Definitions. 2) Tables and grains. 3) CTE plan. 4) Write A/B/C. 5) Dry-run fan-out detectors. 6) Refunds schema + constraint story. 7) Warehouse facts and SCD. 8) Risks: late events, timezone, partial refunds, double counting. Stop when time ends with a clean recap.
articleMode SQL tutorial (practice problems)ModedocsLeetCode Database problems (pattern gym)LeetCodedocsdbt Learn - analytics engineeringdbt LabsdocsPostgreSQL exercises / sample databasesPostgreSQL
Interview habit: before you write a join or window, say the grain of the result set out loud (one row per what?), name the filters that apply before aggregation, and sketch a 4-row toy table. That 30-second ritual catches double-counting and null traps faster than staring at syntax.
When stuck live, reduce to two tables and three rows on the whiteboard. Prove the join, then reintroduce filters and aggregates. Interviewers credit recovery more than a first-draft perfect query that you cannot explain.
State timezone and late-arriving data assumptions once. 'UTC calendar day of paid_at; events may land +2h' prevents half the metric arguments in marketplace and consumer analytics rounds.

Checkpoint

In the capstone, GMV from SUM(orders.amount) after joining order_items without aggregation is dangerous because:

Aorders.amount is always null when items existBItem fan-out repeats each order's header amount, double-counting GMV relative to financeCSUM is non-deterministic without ORDER BY
Sign up free to answer and see why

Checkpoint

First-time buyers in January who also buy again in January - how should monthly first-time buyer count treat them?

ACount them twice: once first-time and once repeat in the same monthBTypically count the buyer once as first-time for that month (based on first paid month), and not also as repeat that month - state the ruleCExclude them from all January metrics until February
Sign up free to answer and see why

Checkpoint

Top category per buyer with possible spend ties - product wants a single deterministic row. Function?

ARANK() ORDER BY spend DESC only, filter rank=1BROW_NUMBER() ORDER BY spend DESC, category_id (or other stable key), keep rn=1CAVG(category_id) grouped by buyer
Sign up free to answer and see why

Checkpoint

Partial refunds allowed multiple times per order item. Schema implication?

AUNIQUE(order_item_id) on refunds so only one refund everBrefunds as a table of events/lines with amounts; enforce sum(succeeded) ≤ item net in app/txn jobsCStore refunds as negative order_items only with the same item primary key
Sign up free to answer and see why

Checkpoint

Warehouse design: finance needs recognition timing; product needs click-to-purchase funnels. Best approach?

AOne wide fact table with every finance and click field nullableBSeparate facts/marts per grain (orders/items, refunds, events) with conformed dims and documented relationshipsCOnly OLTP tables queried live by BI for both needs
Sign up free to answer and see why

Could you run the Swap marketplace mock end-to-end tomorrow with narration, CTE structure, and rubric ≥3 on all axes?

New to itGetting thereConfident

Takeaways

  • Lock metric definitions before any join.
  • Stage CTEs by grain; window for top-N and first/repeat.
  • Refunds are events with sum constraints, not unique one-shots only.
  • Warehouse: separate facts per grain + conformed dims.
  • Self-score the six axes after every mock.

Track complete. Revisit weak lessons; schedule two timed mocks this week on the Swap prompt and one variant.

Sources

Free to read · better with Enzo

Learn it with Enzo

Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.