월별 매출 분해 (신규/기존/복귀)

난이도 어려움 · 권장 25분 · 출력 컬럼 month, new_revenue, existing_revenue, returning_revenue, total_revenue
이 문제 직접 풀어보기 →

문제

월 매출을 세 갈래로 나누세요. new 그 유저의 생애 첫 결제가 일어난 달 existing 직전 달에도 결제한 유저 returning 과거에 결제한 적은 있으나 직전 달에는 결제가 없던 유저 · 세 항목의 합이 total_revenue와 정확히 같아야 합니다. · 월 오름차순으로 정렬하세요.
정렬 month ASC

쉽게 설명하면

이번 달 매출을 통 세 개에 나눠 담기 — 처음 온 손님 / 지난달에도 온 손님 / 오랜만에 다시 온 손님. 세 통을 합치면 전체와 딱 맞아야 한다.

이 사람이어느 통
태어나서 첫 결제가 이번 달신규
지난달에도 결제했음기존
전에 결제한 적 있는데 지난달엔 없음복귀

이 세 칸은 서로 겹치지 않고, 빠지는 경우도 없다. 그래서 합이 딱 맞는다.

이렇게 푼다

  1. 사람마다 생애 첫 결제가 몇 월인지를 구해 둔다.
  2. 결제 줄마다 그 사람의 직전 결제가 몇 월이었나를 한 칸 위에서 끌어온다.
  3. 위 표대로 셋 중 하나를 고른다 — CASE WHEN 첫결제월 = 이번달 THEN '신규' WHEN 직전결제월 = 지난달 THEN '기존' ELSE '복귀' END.
  4. 통별로 금액을 합친다.

이것만 피하면 된다

자세히

묻는 것 · FP&A 의 핵심. 배타적 분류를 만들 줄 아는가.

풀이 순서

  1. 유저 × 월 단위로 먼저 집계한다. 이걸 안 하면 아래가 전부 꼬인다.
  2. MIN(m) OVER (PARTITION BY user_id) → 그 유저의 첫 결제 월.
  3. LAG(m) OVER (PARTITION BY user_id ORDER BY m) → 직전 결제 월.
  4. 첫 달이면 신규. 아니면 직전 달이 바로 전월인지로 기존/복귀를 가른다.

정답 (MySQL)

WITH mu AS (
  SELECT user_id, DATE_FORMAT(paid_date, '%Y-%m-01') AS m, SUM(amount) AS amt
  FROM payments GROUP BY user_id, m
),
l AS (
  SELECT user_id, m, amt,
         LAG(m) OVER (PARTITION BY user_id ORDER BY m) AS prev_m,
         MIN(m) OVER (PARTITION BY user_id)            AS first_m
  FROM mu
)
SELECT DATE_FORMAT(m, '%Y-%m') AS month,
       SUM(CASE WHEN m = first_m THEN amt ELSE 0 END) AS new_revenue,
       SUM(CASE WHEN m <> first_m AND prev_m = DATE_SUB(m, INTERVAL 1 MONTH)
                THEN amt ELSE 0 END) AS existing_revenue,
       SUM(CASE WHEN m <> first_m AND (prev_m IS NULL OR prev_m < DATE_SUB(m, INTERVAL 1 MONTH))
                THEN amt ELSE 0 END) AS returning_revenue,
       SUM(amt) AS total_revenue
FROM l
GROUP BY month
ORDER BY month;

함정

이렇게 변형된다

이탈(churn) 매출까지 요구하면, 직전 달에는 있었는데 이번 달에 없는 유저를 따로 만들어 음수로 붙인다.

← Q4. 가입 코호트별 월간 리텐션Q6. 채널별 첫 결제까지 일수의 중앙값 →