cohort_month ASC3월에 입학한 애들이 1달 뒤·2달 뒤에 몇 명 남았나. 입학한 달마다 따로 추적한다. 반이 다르면 섞지 않는다.
| 가입 월 | 0개월 | 1개월 | 2개월 |
|---|---|---|---|
| 1월 (100명) | 100% | 42% | 28% |
| 2월 (120명) | 100% | 38% | 25% |
가로가 '가입 후 몇 달째', 세로가 '언제 가입한 묶음'. 분모는 그 줄의 전체 인원이고 끝까지 안 바뀐다.
묻는 것 · 코호트 분석의 뼈대. 분모 고정 + 경과 개월.
TIMESTAMPDIFF(MONTH, 코호트, 활동월) 로 경과 개월 M+1, M+2…WITH base AS (
SELECT user_id, DATE_FORMAT(signup_date, '%Y-%m-01') AS cohort FROM users
),
act AS (
SELECT DISTINCT user_id, DATE_FORMAT(event_date, '%Y-%m-01') AS m
FROM events WHERE event_name = 'app_open'
)
SELECT DATE_FORMAT(b.cohort, '%Y-%m') AS cohort_month,
COUNT(DISTINCT b.user_id) AS cohort_size,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN TIMESTAMPDIFF(MONTH, b.cohort, a.m)=1
THEN b.user_id END) / COUNT(DISTINCT b.user_id), 1) AS m1_retention,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN TIMESTAMPDIFF(MONTH, b.cohort, a.m)=2
THEN b.user_id END) / COUNT(DISTINCT b.user_id), 1) AS m2_retention,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN TIMESTAMPDIFF(MONTH, b.cohort, a.m)=3
THEN b.user_id END) / COUNT(DISTINCT b.user_id), 1) AS m3_retention
FROM base b
LEFT JOIN act a ON a.user_id = b.user_id
GROUP BY cohort_month
ORDER BY cohort_month;COUNT(*) 로 쓰면 안 된다. 조인으로 행이 불어나 있다. COUNT(DISTINCT user_id).주 단위면 TIMESTAMPDIFF(WEEK, ...). “결제 리텐션”이면 활동 테이블을 payments 로 바꾼다.