Telemetry
Revenue and billing SQL recipe

Calculate Net and Gross Revenue Retention

Calculate NRR and GRR from account-level monthly recurring-revenue snapshots while keeping expansion out of gross retention.

Intermediateaccount_mrr_snapshotsReviewed 2026-07-28Tested with Apache DataFusion 45.2.0

Question answered

How much starting recurring revenue was retained before and after expansion?

Net retention includes expansion, while gross retention caps each account at its starting value. Reporting both separates customer-base durability from the effect of upsells.

Event contract

Fields the query expects

FieldTypeWhy it exists
timestamp_utcTimestampMonth-end snapshot time.
account_idUtf8Stable billing account identifier.
starting_mrr_usdFloat64MRR at the beginning of the measurement month.
ending_mrr_usdFloat64MRR at the end of the measurement month.
DataFusion SQL

Copy the query

sql
SELECT
  date_trunc('month', timestamp_utc) AS month,
  COUNT(*) AS starting_accounts,
  SUM(starting_mrr_usd) AS starting_mrr_usd,
  SUM(ending_mrr_usd) AS ending_mrr_usd,
  100.0 * SUM(ending_mrr_usd)
    / NULLIF(SUM(starting_mrr_usd), 0) AS net_revenue_retention_pct,
  100.0 * SUM(CASE
    WHEN ending_mrr_usd < 0.0 THEN 0.0
    WHEN ending_mrr_usd > starting_mrr_usd THEN starting_mrr_usd
    ELSE ending_mrr_usd
  END) / NULLIF(SUM(starting_mrr_usd), 0)
    AS gross_revenue_retention_pct
FROM account_mrr_snapshots
WHERE timestamp_utc >= now() - INTERVAL '12 months'
  AND starting_mrr_usd > 0.0
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

Net revenue retention by month

Expansion kept NRR above 100% in May and June, while declining GRR shows increasing contraction or churn underneath.

monthstarting_accountsstarting_mrr_usdending_mrr_usdnet_revenue_retention_pctgross_revenue_retention_pct
2026-05418126,400132,900105.1496.44
2026-06431132,900136,100102.4194.88
2026-07446136,100133,70098.2493.31

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

How the SQL works

  1. 1Only accounts with starting MRR enter the denominator, keeping new business outside retention.
  2. 2NRR compares total ending MRR with starting MRR and therefore includes expansion.
  3. 3GRR caps every account at its starting MRR, so expansion cannot offset contraction or churn.

Edge cases to decide

  • Normalize annual, usage-based, and multi-currency contracts before taking snapshots.
  • Use one account hierarchy consistently when parent and child subscriptions can move independently.
  • Reconcile backdated billing corrections so historical snapshots do not drift silently.

Recommended dashboard

  • Trend: NRR and GRR by month
  • Bars: expansion, contraction, and churn MRR
  • Table: largest account-level revenue changes

Alert guidance

Review a complete month when GRR or NRR falls below its operating range; do not alert on an incomplete current-month snapshot.

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