first_trade_date ASC첫 거래한 날로부터 딱 7일 뒤 다시 왔나. 첫 거래일마다 따로 센다.
| 첫 거래 | 확인할 날 | 왔으면 |
|---|---|---|
| 2월 1일 | 2월 8일 | 재방문 1명 |
| 2월 2일 | 2월 9일 | 아무도 안 옴 → 0 |
'7일 안에' 가 아니라 '딱 그 날'. 2월 7일에 왔어도 안 세고, 2월 9일에 왔어도 안 센다. 재방문 0명인 날도 0 으로 한 줄 나와야 한다.
ON 같은사람 AND 방문일 = 첫거래일 + 7일.COUNT(*) 로 쓰면 안 된다. 붙이면서 늘어난 줄 수가 나온다.묻는 것 · 코호트 테이블을 왼쪽에 두고 활동 로그를 LEFT JOIN 한다. 코호트 문제의 90% 가 이 모양이다.
first_trades)을 왼쪽에 둔다. 분모가 여기서 나온다.LEFT JOIN 하고, 조인 조건에 visit_date = first_trade_date + 7일 을 넣는다.COUNT(DISTINCT f.user_id), 분자는 COUNT(DISTINCT v.user_id). 둘 다 DISTINCT 다.WHERE 에 쓴다 (왼쪽 테이블 조건이므로 안전하다).SELECT f.first_trade_date,
COUNT(DISTINCT f.user_id) AS cohort_size,
COUNT(DISTINCT v.user_id) AS d7_active_users,
ROUND(100.0 * COUNT(DISTINCT v.user_id)
/ NULLIF(COUNT(DISTINCT f.user_id), 0), 1) AS d7_retention_rate
FROM first_trades f
LEFT JOIN daily_app_visits v
ON v.user_id = f.user_id
AND v.visit_date = DATE_ADD(f.first_trade_date, INTERVAL 7 DAY)
WHERE f.first_trade_date BETWEEN '2026-02-01' AND '2026-02-07'
GROUP BY f.first_trade_date
ORDER BY f.first_trade_date;WHERE v.visit_date = ... 로 쓰면 재방문 안 한 유저의 NULL 행이 조건에 걸려 사라지고, 분모까지 같이 깎인다. 오른쪽 테이블 조건은 반드시 ON 에 — 이 한 줄이 리텐션 문제의 핵심이다.= 로 쓸 자리에 <= 를 쓰면 숫자가 크게 부풀어 오른다. 문제를 다시 읽어라.COUNT(*) 는 조인으로 불어난 행 수다. 코호트 크기가 아니다.COUNT 는 빈 입력에서 0 을 돌려주기 때문이다 (SUM 은 NULL 이 된다 — 다른 문제에서 자주 나오는 함정).“D1/D7/D30 을 한 번에”면 조인 조건을 visit_date IN (+1일, +7일, +30일) 로 바꾸고 COUNT(DISTINCT CASE WHEN ... END) 를 세 개 만든다. “월 코호트 매트릭스”면 TIMESTAMPDIFF(MONTH, 코호트월, 활동월) 을 경과 개월로 쓰고 그걸로 피벗한다.