Problems / Monthly Churn Detection / Editorial
SELECT DISTINCT user_id, DATE_TRUNC('month', event_time)
monthly
COUNT(*)
SUM(churned)
month < MAX(month)
churned * 100.0 / active
Churn is a statement about absence, and SQL only stores presence — so churn queries are anti-joins at heart. The two classic bugs are both boundary artifacts: forgetting the last-month guard (everyone "churns" from the final month) and comparing raw timestamps instead of truncated months (a user active May 30 and June 2 must count as retained, which only works after month truncation).
Solve Monthly Churn Detection yourself →