Telemetry
Revenue and billing SQL recipe

Calculate Monthly Recurring Revenue Movement

Separate new, expansion, contraction, churn, and reactivation MRR from billing lifecycle events.

Intermediatebilling_eventsReviewed 2026-07-27Tested with Apache DataFusion 45.2.0

Question answered

What caused recurring revenue to grow or shrink each month?

Net MRR hides why recurring revenue changed. Classifying mutually exclusive movements shows whether growth came from new customers, existing-account expansion, or recovered churn.

Event contract

Fields the query expects

FieldTypeWhy it exists
timestamp_utcTimestampBilling movement time.
account_idUtf8Stable billing account identifier.
movement_typeUtf8new, expansion, contraction, churn, or reactivation.
mrr_change_usdFloat64Signed normalized recurring-revenue change.
DataFusion SQL

Copy the query

sql
SELECT
  date_trunc('month', timestamp_utc) AS month,
  SUM(CASE
    WHEN movement_type IN ('new', 'expansion', 'reactivation')
    THEN mrr_change_usd ELSE 0.0
  END) AS growth_mrr_usd,
  -SUM(CASE
    WHEN movement_type IN ('contraction', 'churn')
    THEN mrr_change_usd ELSE 0.0
  END) AS lost_mrr_usd,
  SUM(mrr_change_usd) AS net_mrr_change_usd,
  COUNT(DISTINCT account_id) AS accounts_changed
FROM billing_events
WHERE timestamp_utc >= now() - INTERVAL '12 months'
GROUP BY date_trunc('month', timestamp_utc)
ORDER BY month;

This read-only query is planned and executed against an empty typed table with Apache DataFusion 45.2.0. The deterministic sample output is synthetic and reviewed separately; validate field types, thresholds, and business definitions against your own data. Read the testing methodology.

Query result

Recurring-revenue growth and loss

July remained positive, but contraction and churn consumed much more of gross growth.

monthgrowth_mrr_usdlost_mrr_usdnet_mrr_change_usdaccounts_changed
2026-0518,4007,20011,20093
2026-0622,1009,80012,300117
2026-0720,70013,4007,300126

Synthetic example output. Run the query against your own event schema and thresholds before using it for operational decisions.

How the SQL works

  1. 1Positive movements combine new, expansion, and reactivation MRR.
  2. 2Lost MRR is negated for a readable positive bar while net change retains the signed total.
  3. 3Distinct account count helps distinguish a broad movement from one unusually large contract.

Edge cases to decide

  • Emit exactly one movement for a billing change or reconcile by provider event ID.
  • Define whether annual contracts contribute normalized monthly value at booking or service time.
  • Separate one-time fees and usage revenue from recurring revenue.

Recommended dashboard

  • Stacked bars: growth_mrr_usd and lost_mrr_usd
  • Trend: net_mrr_change_usd
  • Table: largest account-level movements

Alert guidance

Review when lost MRR consumes an unusually large share of gross growth for a complete month.

Read alert setup

Put the recipe to work

Related instrumentation and guides

Continue the analysis

Run it on real events

Create a table, adapt the fields, and save the result

Start free, send structured events, and use the query result as a chart, shared dashboard widget, or alert input.

Get an API key