Subscription Commerce Analytics Path

MRR is up. Churn is down. The numbers don’t add up.
The CFO wants the MRR walk by Friday. Snapshots disagree with events.

The SQL patterns DTC subscription analysts at Athletic Greens, Hims, Olipop, and Magic Spoon write every week — MRR walk decomposition (new / expansion / contraction / churn / resurrection), cohort retention from event reconstruction, pause-vs-churn forensics (the gotcha that misclassifies ~40% of voluntary cancels in real warehouses), snapshot-vs-event drift reconciliation, NRR for cohorts that change plans, blended subscriber-vs-one-time LTV, and the full multi-CTE MRR walk capstone — against an isolated 24-month replenishment schema with 8 event types, derived daily snapshots, and deliberately-seeded late-arriving events. Modern unit economics for replenishment subscription, on a real-shape event store.

See All Missions

25 missions (10 free · 15 Pro incl. 5 Master) · 8 tables · events + derived snapshots · replenishment DTC

How It Works

1

Read the briefing

A Slack message from your manager

2

Explore the schema

5 tables in a star schema

3

Write your query

Full SQL editor with autocomplete

4

Get expert feedback

Graduated hints, not just pass/fail

Why This Path

Event-Sourced Subscription Schema

Eight canonical event types (created / renewed / skipped / paused / resumed / plan_changed / cancelled / churned) on a real Recurly/Chargebee-shaped event log. Snapshots derived in the seed for the M1–M10 free tier; events drive the M11+ Pro tier.

Pause-vs-Churn Forensics

The most underserved skill in subscription analytics. Real warehouses misclassify 30–40% of voluntary cancels as churn when they’re actually pause-shaped (resumed within 60 days). M20 is the cleanup mission; M22 catches resurrection. The pedagogy nobody else teaches.

Snapshot-vs-Event Reconciliation

Late-arriving events (posted_at != event_ts) drive deliberate drift between the snapshot table and event-derived state. M13 surfaces it; M23 audits it end-to-end. Direct analog of the celebrated Shopify/Stripe/GA4 reconciliation in the ecommerce path.

The MRR Walk Capstone

M25 is opening + new + expansion − contraction − churn = closing in one multi-CTE query — the canonical subscription analytics question every operator ships in their first month, every interview screens on, and every board deck demands.

The Missions

Each mission is a real request from someone at the company. Difficulty increases as you go.

Easy5 missions
Medium5 missions
Hard5 missions
Expert5 missions
Master5 missions

The Database

Replenishment DTC subscription dataset modeled on Athletic Greens / Hims / Olipop event stores. 24-month window (2024-04-01 → 2026-03-31) covering 800 customers (600 subscribers + 200 one-time-only buyers) across 3 plan families (Daily Greens, Recovery Reds, Sleep Stack) and 3 cadences (monthly / quarterly / annual). Events are canonical: 8 event types (created / renewed / skipped / paused / resumed / plan_changed / cancelled / churned), in-place plan-change semantics with from/to plan_id and mrr_delta, voluntary cancellations carrying reason_code (too_expensive / no_longer_needed / switching_competitor / product_quality / other) and involuntary churns carrying failed-payment reasons (failed_payment_after_3_retries / expired_card_no_update / fraud_chargeback). Daily snapshots derived from events at seed time across the last 200 days of the window. 30 deliberately-seeded late-arriving events (event_ts < snapshot_date < posted_at) drive the M13 / M23 reconciliation drift. 25 cancellations seeded with pause-shaped behavior (resumed within 60 days) drive the M20 pause-vs-churn cleanup. 50 plan-change resubscriptions across plan families (90-180 day gap) provide M22 resurrection signal. fact_one_time_orders enables hybrid sub + one-time LTV missions (M17, M24). USD-only. Schema fully isolated as `subscription_commerce` in Postgres; no overlap with the existing ecommerce path.

dim_customers (800)dim_plans (8)dim_skus (22)fact_subscriptions (~650)fact_subscription_events (~6,200)fact_subscription_snapshots (~73,000)fact_charges (~5,000)fact_one_time_orders (~450)

dimension tables   fact tables

MRR is up. Churn is down. The numbers don’t add up.

Write the MRR walk, the pause-vs-churn cleanup, the snapshot-vs-event reconciliation, and the cohort LTV that ties subscriber economics to the brand.

Looking for something different?

|

Subscription Commerce SQL Interview Questions

Subscription analytics interviews are not generic SQL screens. They test whether you can write an MRR walk in one query, derive subscription state from an event log, reconcile a snapshot table against a late-arriving event stream, and reason about the difference between voluntary cancellation and involuntary churn. This guide walks through the SQL skills replenishment DTC analysts get asked about, with worked examples on a realistic Athletic Greens-shape schema and a 25-mission path that drills each pattern end to end.

If you are interviewing for a subscription analyst role at a DTC consumables brand like AG1, Hims, Olipop, Magic Spoon, Daily Harvest, or Care/of, the patterns below are the ones that show up in take-home assignments and live whiteboards.

What subscription data actually looks like

Replenishment subscription warehouses are built around two related grains: an event log and a daily snapshot. The event log is canonical — every state change a subscription goes through (created, renewed, skipped, paused, resumed, plan_changed, cancelled, churned) lands as a row in fact_subscription_events. The snapshot table is derived: at the end of every day, the warehouse rolls events forward and emits one row per still-active or paused subscription into fact_subscription_snapshots. The caseSQL Subscription Commerce database models that exact pair — fact_subscription_events (~6,200 rows across 8 event types), fact_subscription_snapshots (~73,000 rows across the last 200 days), and the supporting facts fact_charges, fact_one_time_orders, and fact_subscriptions.

Around the facts sit three dimensions: dim_customers (with a signup_date and an acquisition_channel — paid_social, paid_search, organic, influencer, referral, email), dim_plans (with plan_family — Daily Greens / Recovery Reds / Sleep Stack — and cadence — monthly / quarterly / annual), and dim_skus (per-plan flavor and size variants).

The mental model that interviewers want you to have is straightforward: events are the truth (what actually happened, when), snapshots are the convenience (state on a given day, easier to query). Most subscription SQL questions ask you to choose between the two — and the senior questions ask you to reconcile the two when they disagree, which they sometimes do because of late-arriving events. That gap is where the interview really lives.

Question patterns you’ll be asked

Across replenishment DTC subscription interviews, six patterns dominate. Each one has a distinct SQL shape, and once you can recognize the shape you can almost always write the query. The worked examples below are deliberately adjacent to the exercises in the mission path — same skill, different angle — so you can read the example, then go practice the variant.

The MRR walk

“Walk me through how MRR changed last quarter” is the most-asked question in subscription interviews. The answer is the MRR walk: opening MRR plus new plus expansion minus contraction minus churn plus resurrection equals closing MRR. The skill being tested is decomposing event types into MRR walk components and reconciling against a snapshot. Every senior subscription analyst has written this query at least once.

Worked example, Q1 2026 MRR walk decomposition:

WITH components AS (
  SELECT
    SUM(CASE WHEN event_type = 'created' THEN mrr_delta ELSE 0 END) AS new_mrr,
    SUM(CASE WHEN event_type = 'plan_changed' AND mrr_delta > 0 THEN mrr_delta ELSE 0 END) AS expansion_mrr,
    SUM(CASE WHEN event_type = 'plan_changed' AND mrr_delta < 0 THEN mrr_delta ELSE 0 END) AS contraction_mrr,
    SUM(CASE WHEN event_type IN ('cancelled','churned') THEN mrr_delta ELSE 0 END) AS churn_mrr,
    SUM(CASE WHEN event_type = 'resumed' THEN mrr_delta ELSE 0 END) AS resurrection_mrr
  FROM fact_subscription_events
  WHERE event_ts BETWEEN '2026-01-01' AND '2026-03-31'
)
SELECT new_mrr, expansion_mrr, contraction_mrr, churn_mrr, resurrection_mrr FROM components;

Practice this in Mission #16 — same MRR walk decomposition with the full sign-convention treatment, and the path capstone (Mission #25) folds opening and closing MRR into a single-row board-pack output.

Pause-vs-churn forensics

Pause-vs-churn cleanup is the highest-leverage pattern in replenishment subscription analytics, and almost no SQL course teaches it. Real warehouses misclassify 30–40% of voluntary cancels as churn when they’re actually pause-shaped (the customer re-subscribes within 60 days). The skill is a self-join on subscription history at the customer grain, with ROW_NUMBER PARTITION BY customer_id to pick the immediate next subscription after a cancel.

Worked example, finding the pause-shaped cohort:

WITH cancels AS (
  SELECT subscription_id AS original_sub_id, customer_id, event_ts AS cancel_date
  FROM fact_subscription_events WHERE event_type = 'cancelled'
),
creates AS (
  SELECT subscription_id AS new_sub_id, customer_id, event_ts AS create_date
  FROM fact_subscription_events WHERE event_type = 'created'
),
paired AS (
  SELECT c.original_sub_id, cr.new_sub_id, c.cancel_date, cr.create_date,
         (cr.create_date::date - c.cancel_date::date) AS gap_days,
         ROW_NUMBER() OVER (PARTITION BY c.original_sub_id ORDER BY cr.create_date) AS rn
  FROM cancels c
  JOIN creates cr ON cr.customer_id = c.customer_id AND cr.create_date > c.cancel_date
)
SELECT * FROM paired WHERE rn = 1 AND gap_days <= 60 ORDER BY gap_days;

Practice this in Mission #20 — the misclassification cleanup that the lifecycle team rescues with re-onboarding interventions.

Cohort retention from events

Cohort retention questions test whether you can pick a subscription’s state at an arbitrary date by replaying its event history. Snapshots only span the last 6–12 months of warehouse history; events are forever. So the senior version of cohort retention always uses events. The skill is a correlated subquery (or a window function) that finds the latest event before month-N and maps the event_type to active / paused / cancelled / churned.

Worked example, 90-day retention from events:

SELECT DATE_TRUNC('month', s.started_at::date) AS cohort_month,
       COUNT(*) AS cohort_size,
       SUM(CASE WHEN
         (SELECT event_type FROM fact_subscription_events e
            WHERE e.subscription_id = s.subscription_id
              AND e.event_ts <= (s.started_at::date + INTERVAL '90 days')::text
            ORDER BY e.event_ts DESC LIMIT 1
         ) IN ('created','renewed','skipped','resumed') THEN 1 ELSE 0 END) AS m3_active
FROM fact_subscriptions s
WHERE s.started_at <= '2025-03-31'
GROUP BY DATE_TRUNC('month', s.started_at::date)
ORDER BY cohort_month;

Practice this in Mission #21 — the full 12-month decay curve with m3 / m6 / m12 active counts per cohort.

Snapshot-vs-event reconciliation

The snapshot-vs-event drift pattern shows up on diligence-grade interviews. Late-arriving events — an event with event_ts on day X but posted_at on day X+10 — cause the snapshot table generated on day X+5 to disagree with the event-derived state at day X+5. The skill is filtering events on both event_ts ≤ snapshot_date and posted_at ≤ snapshot_date, replaying state, and comparing.

Worked example, finding late-arriving events:

SELECT subscription_id, event_type, event_ts, posted_at,
       (posted_at::date - event_ts::date) AS gap_days
FROM   fact_subscription_events
WHERE  posted_at > event_ts
ORDER  BY gap_days DESC, subscription_id ASC;

Practice this in Mission #13 — the introduction; Mission #23 is the full multi-CTE drift report ranked by impact.

Net Revenue Retention by cohort

NRR is the metric every subscription investor asks about. For each signup-month cohort: ratio of current MRR (sum of mrr from latest active snapshot, joined to customers in the cohort) to initial MRR (sum of mrr_delta from created events for customers in the cohort). 100%+ means cohorts are net-expanding; sub-100% means they’re net-contracting. Replenishment DTC typically lands in the 30–60% range at 12-month maturity, so don’t panic when your output isn’t SaaS-grade.

Worked example, NRR by signup cohort:

WITH initial_mrr AS (
  SELECT DATE_TRUNC('month', event_ts::date) AS cohort_month, SUM(mrr_delta) AS initial_mrr
  FROM fact_subscription_events WHERE event_type = 'created'
  GROUP BY DATE_TRUNC('month', event_ts::date)
),
current_mrr AS (
  SELECT DATE_TRUNC('month', c.signup_date::date) AS cohort_month, SUM(s.mrr) AS current_mrr
  FROM fact_subscription_snapshots s
  JOIN dim_customers c ON c.customer_id = s.customer_id
  WHERE s.snapshot_date = '2026-03-31' AND s.state = 'active'
  GROUP BY DATE_TRUNC('month', c.signup_date::date)
)
SELECT i.cohort_month, i.initial_mrr, COALESCE(c.current_mrr, 0) AS current_mrr,
       ROUND(100.0 * COALESCE(c.current_mrr, 0) / NULLIF(i.initial_mrr, 0), 1) AS nrr_pct
FROM initial_mrr i LEFT JOIN current_mrr c USING (cohort_month)
WHERE i.cohort_month < '2025-04-01'
ORDER BY i.cohort_month;

Practice this in Mission #18 — mature cohorts only, 12-month maturity threshold, full NRR percent computed with NULLIF guard.

Subscriber-vs-one-time blended LTV

Hybrid one-time + subscription LTV is the question every modern DTC operator answers in their first quarter. For an acquired cohort, split into subscribers (any successful charge) and one-time-only (zero charges, but one-time orders), compute average LTV for each, and present the multiple. A 3:1 multiple justifies the subscription motion’s CAC; below 2:1 starts a board conversation about whether the subscription product-fit is right.

Worked example, 2024-Q3 cohort split:

WITH cohort AS (
  SELECT customer_id FROM dim_customers
  WHERE signup_date BETWEEN '2024-07-01' AND '2024-09-30'
),
sub_rev AS (
  SELECT c.customer_id, COALESCE(SUM(ch.amount), 0) AS sub_revenue
  FROM cohort c LEFT JOIN fact_charges ch
    ON ch.customer_id = c.customer_id AND ch.status = 'succeeded'
  GROUP BY c.customer_id
),
one_time AS (
  SELECT c.customer_id, COALESCE(SUM(o.total_amount), 0) AS one_time_revenue
  FROM cohort c LEFT JOIN fact_one_time_orders o ON o.customer_id = c.customer_id
  GROUP BY c.customer_id
)
SELECT CASE WHEN s.sub_revenue > 0 THEN 'subscriber'
            WHEN o.one_time_revenue > 0 THEN 'one_time_only' END AS cohort,
       COUNT(*) AS customer_count,
       ROUND(AVG(s.sub_revenue + o.one_time_revenue)::numeric, 2) AS avg_ltv
FROM sub_rev s JOIN one_time o USING (customer_id)
WHERE s.sub_revenue > 0 OR o.one_time_revenue > 0
GROUP BY 1 ORDER BY avg_ltv DESC;

Practice this in Mission #17 — the matched-cohort framing with explicit CASE bucketing. Mission #24 extends the same skill across all mature cohorts for the board pack.

Gotchas that cost you the offer

Six mistakes show up reliably on subscription analytics screens. They’re the difference between a candidate who has “done some SQL” and a candidate who has worked on a real subscription warehouse.

1. Treating ‘cancelled’ and ‘churned’ as the same event

Voluntary cancellation is a product/pricing problem; involuntary churn is a billing/payment-retry problem. Different teams own them, different remediation, different reason codes. Conflating them at standup leads to the wrong team owning the next quarter’s retention work. Always split.

2. Computing MRR from the events table instead of the snapshot

MRR is a stock metric; events are flows. Summing event mrr_delta gives you the MRR motion, not the MRR balance. The latest-day snapshot is the canonical “MRR right now” query. The events table is for walking the change between two snapshots.

3. Counting ‘paused’ subscriptions as active for retention

Paused subs are still customers, but they’re not paying. Including them in the active count for retention purposes inflates the retention rate by 3–5%. Investor norm is “active and paying” — pick that definition and apply it consistently across every cohort query.

4. Ignoring the gap between event_ts and posted_at

event_ts is when the customer-side action happened. posted_at is when the warehouse received it. Real warehouses see 0.5–5% late-arrival rates on retry traffic. A senior question is whether your snapshot-derived state matches your event-derived state on the same date — the answer is usually no because of late events crossing the snapshot generation boundary.

5. Using customer_id for cohort retention when subscription_id is the right grain

A customer can have multiple subscriptions over their lifetime — cancelled their first, re-subscribed three months later, cancelled the second too. Their customer-grain “subscriber state” flips between active and inactive multiple times. Per-subscription retention anchors on subscription_id and started_at; customer-grain retention anchors on customer_id and signup_date. Different questions, different queries.

6. Counting failed charges as revenue

fact_charges has status values of succeeded, failed, and refunded. Failed charges are billing artifacts — attempted, never collected. They’re signal for involuntary-churn forensics, not revenue. The status = ‘succeeded’ filter is non-negotiable on any LTV / blended-revenue / cohort-economics query.

The 25-mission curriculum

The path is structured to walk from snapshot-table fundamentals (free tier, M1–M10) through event-grain pipelines (Hard tier, M11–M15), the unit-economic constructions every CFO ships (Expert tier, M16–M20), and the multi-CTE capstones every senior subscription analyst writes (Master tier, M21–M25). M25 is THE MRR walk in a single multi-CTE query — the metric every subscription interview screens on.

Free tier (M1–M10)

Snapshot table fundamentals. SELECT, JOIN, GROUP BY, DATE_TRUNC, conditional aggregation. By M10 you can query active subs by country, MRR by plan family, and skip rate by plan family.

Hard tier (M11–M15)

Event grain. ROW_NUMBER, LAG, self-joins on the event log. M13 introduces the late-arrival gotcha; M15 is the first multi-CTE plan migration analysis.

Expert tier (M16–M20)

Unit economics. MRR walk decomposition (M16), hybrid LTV (M17), NRR (M18), churn forensics (M19), pause-vs-churn cleanup (M20). Walkthroughs on every mission.

Master tier (M21–M25)

Capstones. 12-month decay curve (M21), resurrection identification (M22), snapshot-vs-event drift report (M23), full subscriber LTV (M24), and THE MRR walk(M25) — the metric every interview screens on.

Frequently asked

What is the MRR walk in SQL?

The MRR walk decomposes the change in monthly recurring revenue between two points in time into five (sometimes six) components: new MRR (created events), expansion (plan_changed events with positive mrr_delta), contraction (plan_changed with negative), churn (cancelled + churned events), and resurrection (resumed events). In SQL it’s a single CTE with five parallel SUM(CASE WHEN ...) expressions, cross-joined with two scalar CTEs for opening and closing MRR from the snapshot table.

What is pause-vs-churn cleanup?

Real subscription warehouses misclassify 30–40% of voluntary cancels as permanent churn when in fact the customer re-subscribes within 60 days — the cancel was effectively a pause. Cleanup is a self-join on customer history that finds (cancel, re-create) pairs with a short gap, identifies the misclassified subscriptions, and routes the customers to a re-onboarding flow.

How does NRR differ from retention rate?

Retention rate is a count ratio (customers still active divided by cohort size). NRR is a revenue ratio (current MRR from cohort divided by initial MRR from cohort). They diverge sharply when plan changes happen — a customer who downgraded from Daily Greens Annual ($59/mo) to Sleep Stack Monthly ($49/mo) is still retained but contributes -$10 to the NRR numerator. Senior interviewers ask both to test whether you understand the difference.

Why are events the source of truth and not the snapshot table?

The snapshot is derived from events at warehouse-roll time. Events are forever (until archive); snapshots typically retain 6–12 months of history. For any question that needs state at a date older than the snapshot retention window (cohort retention on 12+ month-old cohorts, year-over-year MRR walk), events are the only available source. The snapshot is a convenience for current-state queries.

What does a subscription analyst SQL test look like?

Most are 60–90-minute take-homes against a star-schema mock of an event log and snapshot table. Expect three or four tasks: a current-state KPI roll-up (MRR by plan, ARPU, active-by-country), a cohort retention or NRR calculation, a churn forensics breakdown, and one stretch question that’s either the MRR walk or a snapshot-vs-event reconciliation. The companies that hire on this shape: Athletic Greens, Hims/Hers, Olipop, Magic Spoon, Daily Harvest, Care/of, Trade Coffee, Pact Coffee, Quip, Ro, plus the platform side at Stripe Billing, Recurly, and Chargebee.

Ready to drill the patterns?

Twenty-five missions, real Postgres schema, instant feedback. Walk from snapshot inventory by plan through event-grain reconciliation and the capstone MRR walk every senior subscription analyst has written.