[SQL/코드카타] 프로그래머스 - 상품을 구매한 회원 비율 구하기

문제

https://school.programmers.co.kr/learn/courses/30/lessons/131534

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr


✍️ 문제 요약

  • 2021년에 가입한 전체 회원들 중 상품을 구매한 회원수와 상품을 구매한 회원의 비율
  • (2021년에 가입한 회원 중 상품을 구매한 회원수 / 2021년에 가입한 전체 회원 수)
  • 구매날짜의 년, 월로 출력
  • 소수 둘째자리에서 반올림
  • 년을 기준으로 오름차순, 월을 기준으로 오름차순

▼ 결과 예시


내가 작성한 코드

WITH y_2021 AS (
    select *
    from user_info
    where joined between '2021-01-01' and '2021-12-31'
    group by user_id 
)
SELECT DATE_FORMAT(sales_date, '%Y') YEAR,
       DATE_FORMAT(sales_date, '%m') MONTH,
       count(distinct y.user_id) PURCHASED_USERS,
       (count(distinct os.user_id) / count(*))*100 AS PURCHASED_RATIO
FROM y_2021      as y
JOIN online_sale as os ON y.user_id = os.user_id
GROUP BY 1, 2
ORDER BY YEAR ASC, MONTH ASC
🔍 실행 결과
무언가 "상품을 구매한 회원 비율"을 구하는 것에서 문제가 생긴 듯 하다.
🚨 문제점
   - 구매한 회원의 비율의 논리적 구조가 잘못되었음
   - purchased_ratio(비율)에서는 정답예시와 같이 나와야 하므로, *100 을 하면 안됨.

정답 코드

1. CROSS JOIN 을 사용한 방법

WITH y_2021 AS (                                 -- 2021년 가입한 회원 목록
    SELECT user_id
    FROM user_info
    WHERE joined BETWEEN '2021-01-01' AND '2021-12-31'
),
count_2021 AS (                                  -- 2021년 가입한 회원 수
    SELECT COUNT(*) AS total_users
    FROM y_2021
)
SELECT 
  YEAR(os.sales_date) AS YEAR,
  MONTH(os.sales_date) AS MONTH,
  COUNT(DISTINCT os.user_id) AS PURCHASED_USERS,
  ROUND(COUNT(DISTINCT os.user_id) / c.total_users, 1) AS PURCHASED_RATIO
FROM online_sale os
JOIN y_2021 y ON os.user_id = y.user_id
CROSS JOIN count_2021 c                            -- CROSS JOIN 사용
GROUP BY YEAR, MONTH, c.total_users
ORDER BY YEAR, MONTH;

 

2. 서브쿼리를 사용한 방법 ⭐️⭐️⭐️ 

WITH y_2021 AS (
    SELECT user_id
    FROM user_info
    WHERE joined BETWEEN '2021-01-01' AND '2021-12-31'
)
SELECT 
  YEAR(os.sales_date) AS YEAR,
  MONTH(os.sales_date) AS MONTH,
  COUNT(DISTINCT os.user_id) AS PURCHASED_USERS,
  ROUND(
    COUNT(DISTINCT os.user_id) / (SELECT COUNT(*) FROM y_2021),
    2
  ) AS PURCHASED_RATIO
FROM online_sale os
JOIN y_2021 y ON os.user_id = y.user_id
GROUP BY YEAR, MONTH
ORDER BY YEAR, MONTH;

 

3. 또 다른 풀이법 : CROSS JOIN의 다른 표현법

WITH TOTAL AS ( -- 2021년 가입자 수
    SELECT COUNT(*)  AS CNT
      FROM USER_INFO A
     WHERE A.JOINED LIKE '2021%'
)       
SELECT YEAR(B.SALES_DATE) AS YEAR
     , MONTH(B.SALES_DATE) AS MON
     , COUNT(DISTINCT B.USER_ID) AS PURCHASED_USERS
     , ROUND((COUNT(DISTINCT B.USER_ID)/T.CNT), 1) AS PUCHASED_RATIO
  FROM ONLINE_SALE B
     , USER_INFO A
     , TOTAL T
 WHERE B.USER_ID = A.USER_ID
   AND A.JOINED LIKE '2021%'
 GROUP BY YEAR, MON
 ORDER BY YEAR, MON

틈새 문법

1) CROSS JOIN

▷ 언제사용? "모든 조합을 한 번에 만들고 싶을 때" 또는 "한 테이블의 상수(값 1개)를 모든 행에 붙이고 싶을 때" 유용

더보기

🧩 4️⃣ INNER JOIN과의 차이

구분 CROSS JOIN INNER JOIN
조인 조건 없음 있음 (ON 필요)
결과 테이블의 조건을 만족하는 행만
사용 목적 모든 조합 / 상수 붙이기 조건에 맞는 데이터 결합
예시 문법 FROM A CROSS JOIN B FROM A JOIN B ON A.id=B.id

🧩 5️⃣ 주의할 점

  • 행이 많을수록 폭발적으로 증가 → 조인 결과 수 = A행수 × B행수
    (즉, 1행짜리 상수 테이블이 아닐 경우 매우 위험⚠️)
  • 조인 조건이 없으므로 실수로 잘못 쓰면 수천만 행 생길 수 있음

 

정리 요약

특징 설명
정의 테이블의 모든 행을 서로 조합
결과 A행수 × B행수
사용 상수(1행짜리) 테이블을 전체 결과에 붙이기, 모든 조합 생성
주의점 조건이 없기 때문에 폭발적 증가 위험
SELECT * FROM tableA CROSS JOIN tableB;
=     -- 같은 표현
SELECT * FROM tableA, tableB;