한 번도 결제하지 않은 유저 수

난이도 쉬움 · 권장 7분 · 출력 컬럼 channel, never_paid
이 문제 직접 풀어보기 →

문제

채널별로 '결제 이력이 한 건도 없는 유저 수' 를 구하세요. channel never_paid 그 채널에서 결제 이력이 없는 유저 수 · payments 테이블에는 user_id 가 비어 있는(NULL) 행이 섞여 있을 수 있습니다. 그 행은 '누구의 결제인지 알 수 없는 행' 이므로 결제 이력으로 치지 않습니다. · 결제 이력이 없는 유저가 0 명인 채널도 0 으로 한 행 나와야 합니다. · never_paid 내림차순, 같으면 channel 오름차순으로 정렬하세요.
정렬 never_paid DESC, channel ASC

쉽게 설명하면

명단에서 '숙제 안 낸 사람' 찾기. 쉬워 보이는데 함정이 하나 있고, 그것만 보는 문제다.

이게 함정이다.

SELECT 5 WHERE 5 NOT IN (1, 2, NULL) 0행

왜 그런가 — 이 식은 이렇게 풀린다.
5≠1(참) 그리고 5≠2(참) 그리고 5≠NULL(모름)
마지막을 모르니까 전체가 '모름' 이 되고, 조건에서 '모름' 은 거짓 취급이라 전부 탈락한다. 한 줄도 안 나온다.

이렇게 푼다

  1. 방법 1 — NOT EXISTS (SELECT 1 FROM payments p WHERE p.user_id = u.user_id)
  2. 방법 2 — 결제자 목록을 왼쪽 붙이기 하고 안 붙은 것만 센다 (p.user_id IS NULL).
  3. 채널별로 SUM(CASE WHEN p.user_id IS NULL THEN 1 ELSE 0 END).

이것만 피하면 된다

자세히

묻는 것 · '존재하지 않는 것 찾기'. 쉬워 보이지만 NOT IN 을 쓰면 데이터에 NULL 이 하나 섞인 순간 결과가 0행이 된다. 이 함정 하나만 아는지 보는 문제다.

풀이 순서

  1. 결제한 유저 목록을 만든다. user_id 가 NULL 인 행은 뺀다 (누구의 결제인지 모르는 행이므로).
  2. users LEFT JOIN 그 목록 → 붙지 않은 유저가 '결제 없음' 이다.
  3. SUM(CASE WHEN p.user_id IS NULL THEN 1 ELSE 0 END) 으로 센다. COUNT(*) 로 세면 전체 유저 수가 나온다.
  4. 0 명인 채널도 한 행 나와야 하므로 WHERE 로 걸러내지 말고 CASE 로 센다.

정답 (MySQL)

-- 방법 1. LEFT JOIN ... IS NULL (0명인 채널도 행이 남는다)
SELECT u.channel,
       SUM(CASE WHEN p.user_id IS NULL THEN 1 ELSE 0 END) AS never_paid
FROM users u
LEFT JOIN (SELECT DISTINCT user_id FROM payments WHERE user_id IS NOT NULL) p
       ON p.user_id = u.user_id
GROUP BY u.channel
ORDER BY never_paid DESC, u.channel ASC;

-- 방법 2. NOT EXISTS — NULL 에 면역이라 IS NOT NULL 을 안 써도 된다
SELECT u.channel,
       SUM(CASE WHEN NOT EXISTS (SELECT 1 FROM payments p
                                  WHERE p.user_id = u.user_id)
                THEN 1 ELSE 0 END) AS never_paid
FROM users u
GROUP BY u.channel
ORDER BY never_paid DESC, u.channel ASC;

함정

이렇게 변형된다

“이탈 유저 찾기”, “한 번도 로그인 안 한 유저”, “재구매 없는 유저” 전부 같은 패턴. 반대로 “결제한 유저만”이면 EXISTS 또는 INNER JOIN + DISTINCT.

← Q18. 채널별 ARPU 와 ARPPUQ20. 유저별 결제 요약 (NULL 표시와 정렬 분리) →