[프로그래머스] ⭐⭐⭐⭐ 저자 별 카테고리 별 매출액 집계하기 (MySQL)
📌 문제 설명
다음은 어느 한 서점에서 판매 중인 도서들의 도서 정보(BOOK), 저자 정보(AUTHOR), 판매 정보(BOOK_SALES) 테이블입니다.
BOOK 테이블
| Column name | Type | Nullable | Description |
|---|---|---|---|
| BOOK_ID | INTEGER | FALSE | 도서 ID |
| CATEGORY | VARCHAR(N) | FALSE | 카테고리 (경제, 인문, 소설, 생활, 기술) |
| AUTHOR_ID | INTEGER | FALSE | 저자 ID |
| PRICE | INTEGER | FALSE | 판매가 (원) |
| PUBLISHED_DATE | DATE | FALSE | 출판일 |
AUTHOR 테이블
| Column name | Type | Nullable | Description |
|---|---|---|---|
| AUTHOR_ID | INTEGER | FALSE | 저자 ID |
| AUTHOR_NAME | VARCHAR(N) | FALSE | 저자명 |
BOOK_SALES 테이블
| Column name | Type | Nullable | Description |
|---|---|---|---|
| BOOK_ID | INTEGER | FALSE | 도서 ID |
| SALES_DATE | DATE | FALSE | 판매일 |
| SALES | INTEGER | FALSE | 판매량 |
📌 문제
2022년 1월의 도서 판매 데이터를 기준으로 저자 별, 카테고리 별 매출액(TOTAL_SALES = 판매량 * 판매가)을 구하여, 저자 ID(AUTHOR_ID), 저자명(AUTHOR_NAME), 카테고리(CATEGORY), 매출액(TOTAL_SALES) 리스트를 출력하는 SQL문을 작성해주세요.
결과는 저자 ID를 기준으로 오름차순, 저자 ID가 같다면 카테고리를 기준으로 내림차순 정렬해주세요.
📌 예시
BOOK 테이블, AUTHOR 테이블, BOOK_SALES 테이블이 다음과 같을 때
BOOK
| BOOK_ID | CATEGORY | AUTHOR_ID | PRICE | PUBLISHED_DATE |
|---|---|---|---|---|
| 1 | 인문 | 1 | 10000 | 2020-01-01 |
| 2 | 경제 | 1 | 9000 | 2021-02-05 |
| 3 | 경제 | 2 | 9000 | 2021-03-11 |
AUTHOR
| AUTHOR_ID | AUTHOR_NAME |
|---|---|
| 1 | 홍길동 |
| 2 | 김영호 |
BOOK_SALES
| BOOK_ID | SALES_DATE | SALES |
|---|---|---|
| 1 | 2022-01-01 | 2 |
| 2 | 2022-01-02 | 3 |
| 1 | 2022-01-05 | 1 |
| 2 | 2022-01-20 | 5 |
| 2 | 2022-01-21 | 6 |
| 3 | 2022-01-22 | 2 |
| 2 | 2022-02-11 | 3 |
2022년 1월 도서별 총 매출액은 도서 ID 1이 3권 × 10,000원 = 30,000원, 도서 ID 2가 14권 × 9,000원 = 126,000원, 도서 ID 3이 2권 × 9,000원 = 18,000원입니다.
SQL을 실행하면 다음과 같이 출력되어야 합니다.
| AUTHOR_ID | AUTHOR_NAME | CATEGORY | TOTAL_SALES |
|---|---|---|---|
| 1 | 홍길동 | 인문 | 30000 |
| 1 | 홍길동 | 경제 | 126000 |
| 2 | 김영호 | 경제 | 18000 |
📌 핵심 포인트
이 문제는 3개 테이블을 연결해야 합니다. 각 테이블의 역할을 먼저 파악하는 것이 중요합니다.
BOOK_SALES→ 2022년 1월 판매량(SALES) 제공BOOK→ 도서별 판매가(PRICE)와 카테고리(CATEGORY) 제공AUTHOR→ 저자 ID에 대응하는 저자명(AUTHOR_NAME) 제공
💡 Tip. 매출액 =
판매량(SALES) × 판매가(PRICE)인데, 두 컬럼이 각각BOOK_SALES와BOOK에 흩어져 있습니다. JOIN 후 곱셈을 수행해야 한다는 점을 기억하세요.
코드
ver(1) - CTE + JOIN 활용
핵심 아이디어: CTE로 BOOK_SALES에서 2022년 1월 도서별 총 판매량을 먼저 집계한 뒤, BOOK과 AUTHOR 테이블을 JOIN하여 저자별·카테고리별 매출액 합계를 계산합니다. 쿼리를 “판매량 집계 → 매출액 계산” 두 단계로 분리해 가독성이 높은 풀이입니다.
-- CTE: 2022년 1월의 도서별 총 판매량을 미리 집계
WITH MonthlySales AS (
SELECT
BOOK_ID,
SUM(SALES) AS TOTAL_AMOUNT -- 도서별 1월 총 판매량 합산
FROM BOOK_SALES
WHERE SALES_DATE LIKE '%2022-01%' -- 2022년 1월 데이터만 필터링
GROUP BY BOOK_ID -- 도서 ID별로 묶어서 집계
)
-- 메인 쿼리: CTE, BOOK, AUTHOR를 JOIN하여 저자별·카테고리별 매출액 산출
SELECT
B.AUTHOR_ID,
A.AUTHOR_NAME,
B.CATEGORY,
SUM(M.TOTAL_AMOUNT * B.PRICE) AS TOTAL_SALES -- 판매량 × 판매가 = 매출액 합계
FROM MonthlySales AS M
JOIN BOOK B ON M.BOOK_ID = B.BOOK_ID -- 판매 데이터 ↔ 도서 정보 연결
JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID -- 도서 정보 ↔ 저자 정보 연결
GROUP BY B.AUTHOR_ID, B.CATEGORY -- 저자별, 카테고리별로 묶어 집계
ORDER BY B.AUTHOR_ID ASC, B.CATEGORY DESC; -- 저자 ID 오름차순, 카테고리 내림차순
💡 Tip. CTE를 쓰는 이유 날짜 필터링과 판매량 집계를 CTE에 분리해두면 두 가지 이점이 있습니다.
- 성능: 메인 쿼리가 JOIN하기 전에 데이터 행 수가 줄어 처리 비용이 감소합니다.
- 가독성: “1월 판매량 집계 → 매출액 계산”의 흐름이 명확하게 보입니다.
ver(2) - 인라인 뷰(서브쿼리) + JOIN 활용
핵심 아이디어: CTE 대신 FROM 절 안에 서브쿼리를 직접 작성하는 인라인 뷰 방식입니다. CTE와 논리적으로 동일하지만, WITH 문 없이 한 쿼리 안에 모두 담고 싶을 때 사용합니다.
-- FROM 절의 서브쿼리(인라인 뷰)가 CTE 역할을 대신함
SELECT
B.AUTHOR_ID,
A.AUTHOR_NAME,
B.CATEGORY,
SUM(M.TOTAL_AMOUNT * B.PRICE) AS TOTAL_SALES -- 판매량 × 판매가 = 매출액 합계
FROM (
-- 인라인 뷰: 2022년 1월 도서별 총 판매량 집계
SELECT
BOOK_ID,
SUM(SALES) AS TOTAL_AMOUNT -- 도서별 1월 총 판매량 합산
FROM BOOK_SALES
WHERE SALES_DATE LIKE '%2022-01%' -- 2022년 1월 데이터만 필터링
GROUP BY BOOK_ID -- 도서 ID별로 묶어서 집계
) AS M
JOIN BOOK B ON M.BOOK_ID = B.BOOK_ID -- 인라인 뷰 ↔ 도서 정보 연결
JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID -- 도서 정보 ↔ 저자 정보 연결
GROUP BY B.AUTHOR_ID, B.CATEGORY -- 저자별, 카테고리별로 묶어 집계
ORDER BY B.AUTHOR_ID ASC, B.CATEGORY DESC; -- 저자 ID 오름차순, 카테고리 내림차순
💡 Tip. CTE vs 인라인 뷰 두 방식은 실행 결과가 동일합니다. CTE(
WITH)는 쿼리 상단에 미리 정의하므로 재사용이 가능하고 가독성이 좋습니다. 인라인 뷰는 별도의 WITH 절 없이 FROM 절 안에 바로 작성하므로 쿼리가 짧을 때 간결하게 쓸 수 있습니다.
ver(3) - JOIN만으로 한 번에 처리
핵심 아이디어: CTE나 서브쿼리 없이 3개 테이블을 바로 JOIN한 뒤, WHERE로 날짜를 필터링하고 GROUP BY로 집계합니다. 가장 짧고 단순한 풀이이지만, BOOK_SALES의 날짜별 행을 JOIN 단계에서 모두 펼친 후 집계하므로 데이터가 많을수록 처리 비용이 늘어날 수 있습니다.
-- 3개 테이블을 바로 JOIN하고 WHERE + GROUP BY로 한 번에 집계
SELECT
B.AUTHOR_ID,
A.AUTHOR_NAME,
B.CATEGORY,
SUM(BS.SALES * B.PRICE) AS TOTAL_SALES -- 날짜별 판매량 × 판매가를 합산
FROM BOOK_SALES BS
JOIN BOOK B ON BS.BOOK_ID = B.BOOK_ID -- 판매 데이터 ↔ 도서 정보 연결
JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID -- 도서 정보 ↔ 저자 정보 연결
WHERE BS.SALES_DATE LIKE '%2022-01%' -- 2022년 1월 데이터만 필터링
GROUP BY B.AUTHOR_ID, B.CATEGORY -- 저자별, 카테고리별로 묶어 집계
ORDER BY B.AUTHOR_ID ASC, B.CATEGORY DESC; -- 저자 ID 오름차순, 카테고리 내림차순
⚠️ 주의. ver(1), ver(2)는 먼저 판매량을 도서별로 합산한 뒤 PRICE를 곱합니다. ver(3)은 JOIN 후 날짜별 행 각각에 PRICE를 곱한 뒤 SUM으로 합산합니다. 수학적으로는 결과가 동일하지만(
SUM(a × c) = SUM(a) × c, 단 c가 상수일 때), JOIN 규모 면에서는 ver(1), ver(2)가 더 효율적입니다.
📊 풀이법 비교 요약
| ver(1) CTE + JOIN | ver(2) 인라인 뷰 + JOIN | ver(3) JOIN만 활용 | |
|---|---|---|---|
| 가독성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 성능 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 코드 길이 | 보통 | 보통 | 짧음 |
| 재사용성 | ✅ CTE 재사용 가능 | ❌ | ❌ |
| MySQL 버전 | 8.0+ 권장 | 모든 버전 | 모든 버전 |
💡 어떤 풀이를 써야 할까?
- 코딩 테스트에서는 ver(3)이 가장 빠르게 작성할 수 있어 실용적입니다.
- 실무나 복잡한 쿼리에서는 ver(1)처럼 CTE로 단계를 분리하면 유지보수가 쉽습니다.
- ver(2)는 MySQL 구버전 환경에서 CTE를 쓸 수 없을 때 대안으로 사용합니다.
📊 쿼리 실행 흐름 요약 (ver(1) 기준)
BOOK_SALES
└─ WHERE 2022-01 필터링
└─ GROUP BY BOOK_ID → SUM(SALES)
↓ CTE: MonthlySales
JOIN BOOK → CATEGORY, PRICE, AUTHOR_ID 확보
↓
JOIN AUTHOR → AUTHOR_NAME 확보
↓
GROUP BY AUTHOR_ID, CATEGORY
↓
SUM(TOTAL_AMOUNT × PRICE) = TOTAL_SALES
↓
ORDER BY AUTHOR_ID ASC, CATEGORY DESC
댓글남기기