SQL Analysis — Customer Churn Analysis¶
Setup note
Load the cleaned dataset into your database:
CREATE TABLE churn (
customerID VARCHAR(20), gender VARCHAR(10), SeniorCitizen VARCHAR(3),
Partner VARCHAR(3), Dependents VARCHAR(3), tenure INT,
PhoneService VARCHAR(3), InternetService VARCHAR(20),
Contract VARCHAR(20), PaperlessBilling VARCHAR(3),
PaymentMethod VARCHAR(40), MonthlyCharges DECIMAL(8,2),
TotalCharges DECIMAL(10,2), Churn INT,
tenure_bucket VARCHAR(30), addon_count INT, auto_payment INT
);
Q1 — What is the Overall Churn Rate?¶
SELECT
COUNT(*) AS total_customers,
SUM(Churn) AS churned_customers,
ROUND(AVG(Churn) * 100, 2) AS churn_rate_pct,
ROUND(SUM(CASE WHEN Churn = 0 THEN MonthlyCharges END), 2) AS retained_mrr,
ROUND(SUM(CASE WHEN Churn = 1 THEN MonthlyCharges END), 2) AS lost_mrr
FROM churn;
Sample output:
| total_customers | churned_customers | churn_rate_pct | retained_mrr | lost_mrr |
|---|---|---|---|---|
| 7043 | 1869 | 26.54 | 362,827.40 | 139,130.55 |
Q2 — Churn Rate by Contract Type¶
SELECT
Contract,
COUNT(*) AS customers,
SUM(Churn) AS churned,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
ROUND(AVG(MonthlyCharges), 2) AS avg_monthly_charge
FROM churn
GROUP BY Contract
ORDER BY churn_rate_pct DESC;
Sample output:
| Contract | customers | churned | churn_rate_pct | avg_monthly_charge |
|---|---|---|---|---|
| Month-to-month | 3875 | 1655 | 42.7 | 66.40 |
| One year | 1473 | 166 | 11.3 | 65.05 |
| Two year | 1695 | 48 | 2.8 | 60.77 |
Key finding
Month-to-month customers churn at 15× the rate of two-year contract customers (42.7% vs 2.8%). Converting even 10% of month-to-month customers to a one-year contract could cut total churn by 4 percentage points.
Q3 — Churn Rate by Tenure Bucket¶
SELECT
tenure_bucket,
COUNT(*) AS customers,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
ROUND(AVG(MonthlyCharges), 2) AS avg_charge,
ROUND(AVG(addon_count), 1) AS avg_addons
FROM churn
GROUP BY tenure_bucket
ORDER BY
CASE tenure_bucket
WHEN 'New (< 6mo)' THEN 1
WHEN 'Growing (6-24mo)' THEN 2
WHEN 'Established (2-4yr)'THEN 3
WHEN 'Loyal (4+ yr)' THEN 4
END;
Sample output:
| tenure_bucket | customers | churn_rate_pct | avg_charge | avg_addons |
|---|---|---|---|---|
| New (< 6mo) | 1099 | 47.4 | 67.65 | 1.8 |
| Growing (6-24mo) | 1820 | 33.6 | 65.20 | 2.1 |
| Established (2-4yr) | 1756 | 21.5 | 64.80 | 2.4 |
| Loyal (4+ yr) | 2368 | 5.2 | 59.30 | 2.8 |
Q4 — Internet Service and Churn¶
SELECT
InternetService,
COUNT(*) AS customers,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
ROUND(AVG(MonthlyCharges), 2) AS avg_monthly_charge
FROM churn
GROUP BY InternetService
ORDER BY churn_rate_pct DESC;
Sample output:
| InternetService | customers | churn_rate_pct | avg_monthly_charge |
|---|---|---|---|
| Fiber optic | 3096 | 41.9 | 91.37 |
| DSL | 2421 | 19.0 | 57.18 |
| No | 1526 | 7.4 | 21.00 |
Q5 — Impact of Add-Ons on Churn¶
SELECT
addon_count,
COUNT(*) AS customers,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
ROUND(AVG(MonthlyCharges), 2) AS avg_charge
FROM churn
GROUP BY addon_count
ORDER BY addon_count;
Q6 — Payment Method and Churn¶
SELECT
PaymentMethod,
COUNT(*) AS customers,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
CASE
WHEN PaymentMethod IN ('Bank transfer (automatic)', 'Credit card (automatic)')
THEN 'Automatic' ELSE 'Manual'
END AS payment_type
FROM churn
GROUP BY PaymentMethod
ORDER BY churn_rate_pct DESC;
Sample output:
| PaymentMethod | customers | churn_rate_pct | payment_type |
|---|---|---|---|
| Electronic check | 2365 | 45.3 | Manual |
| Mailed check | 1612 | 19.1 | Manual |
| Bank transfer (automatic) | 1544 | 16.7 | Automatic |
| Credit card (automatic) | 1522 | 15.2 | Automatic |
Q7 — At-Risk Customers: Current Month-to-Month with High Churn Signals¶
-- Find current month-to-month customers who match the profile of churned customers
SELECT
customerID,
tenure,
tenure_bucket,
Contract,
InternetService,
MonthlyCharges,
addon_count,
PaymentMethod,
-- Simple churn score (higher = more at risk)
(
CASE Contract WHEN 'Month-to-month' THEN 30 ELSE 0 END +
CASE WHEN tenure < 12 THEN 20 ELSE 0 END +
CASE InternetService WHEN 'Fiber optic' THEN 15 ELSE 0 END +
CASE WHEN MonthlyCharges > 70 THEN 10 ELSE 0 END +
CASE WHEN addon_count = 0 THEN 15 ELSE 0 END +
CASE WHEN auto_payment = 0 THEN 10 ELSE 0 END
) AS churn_risk_score
FROM churn
WHERE Churn = 0 -- current customers only (not already churned)
AND Contract = 'Month-to-month'
ORDER BY churn_risk_score DESC
LIMIT 100;
Q8 — Senior Citizens¶
SELECT
SeniorCitizen,
COUNT(*) AS customers,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct,
ROUND(AVG(MonthlyCharges), 2) AS avg_charge
FROM churn
GROUP BY SeniorCitizen;
Q9 — Cohort Analysis: Churn Rate by Signup Month¶
-- Requires a signup_date column — add to dataset if available
-- This simulates the cohort query structure
WITH cohorts AS (
SELECT
customerID,
FLOOR(tenure / 6) * 6 AS months_at_signup_bucket,
Churn
FROM churn
)
SELECT
months_at_signup_bucket,
COUNT(*) AS cohort_size,
ROUND(AVG(Churn) * 100, 1) AS churn_rate_pct
FROM cohorts
GROUP BY months_at_signup_bucket
ORDER BY months_at_signup_bucket;