(한 행)들어온 사람 → 구경한 사람 → 장바구니에 담은 사람 → 산 사람. 각 칸에 몇 명.
| 단계 | 사람 수 | 앞 단계 대비 |
|---|---|---|
| app_open | 2,834 | |
| item_view | 1,872 | 66.1% |
| add_to_cart | 837 | 44.7% |
| purchase | 320 | 38.2% |
표 하나에 한 줄로 나와야 한다. 테이블은 한 번만 읽으면 된다.
COUNT(DISTINCT CASE WHEN 행동='app_open' THEN user_id END) 식으로 네 개.ROUND(100.0 * 뒤 / NULLIF(앞, 0), 1).DISTINCT 를 빼면 안 된다. 한 사람이 app_open 을 네 번 남기면 4 로 세어진다. 묻는 건 사람 수다.CASE 에 ELSE 0 을 쓰지 마라. 0 도 값이라서 '0 번 사람' 한 명으로 세어진다. ELSE 를 안 쓰면 NULL 이 되고, COUNT 는 NULL 을 안 센다.묻는 것 · 한 테이블을 한 번만 읽어 네 숫자를 뽑고, 그 숫자끼리 나눈다. JOIN 도 GROUP BY 도 필요 없다. 여기서 시간을 쓰면 안 되는 문제다.
COUNT(DISTINCT CASE WHEN ... THEN user_id END) 로 단계별 순 유저 수를 한 줄씩 만든다. 테이블을 네 번 읽을 필요가 없다.100.0 * 을 앞에 붙이고 ROUND(..., 1).NULLIF(분모, 0) 을 씌운다.SELECT a AS app_open_users,
v AS item_view_users,
k AS cart_users,
p AS purchase_users,
ROUND(100.0 * v / NULLIF(a, 0), 1) AS view_conversion_rate,
ROUND(100.0 * k / NULLIF(v, 0), 1) AS cart_conversion_rate,
ROUND(100.0 * p / NULLIF(k, 0), 1) AS purchase_conversion_rate
FROM (
SELECT COUNT(DISTINCT CASE WHEN event_name = 'app_open' THEN user_id END) AS a,
COUNT(DISTINCT CASE WHEN event_name = 'item_view' THEN user_id END) AS v,
COUNT(DISTINCT CASE WHEN event_name = 'add_to_cart' THEN user_id END) AS k,
COUNT(DISTINCT CASE WHEN event_name = 'purchase' THEN user_id END) AS p
FROM user_event_logs
) c;COUNT(CASE ...) 는 4 를 센다. 묻는 건 유저 수이지 이벤트 수가 아니다.ELSE 0 을 붙이면 0 도 값이라서 COUNT(DISTINCT ...) 가 0 을 하나의 유저로 센다. ELSE 를 안 쓰면 NULL 이 되고 COUNT 는 NULL 을 세지 않는다.100 * v / a 는 정수 나눗셈이 되는 엔진에서 0 이 나온다. 토스 테스트돔에서 실제로 이걸로 틀린 적이 있다.“단계별 이탈률”은 100 − 전환율. “채널별 퍼널”이면 GROUP BY channel 만 추가되고 구조는 그대로다. 순서를 강제하는 진짜 퍼널은 단계별 MIN(created_at) 을 구해 MIN(cart) > MIN(view) 처럼 비교한다.