
문제
https://school.programmers.co.kr/learn/courses/30/lessons/151141
프로그래머스
SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
문제 설명
- CAR_RENTAL_COMPANY_CAR 테이블과 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블과
CAR_RENTAL_COMPANY_DISCOUNT_PLAN 테이블에서 자동차 종류가 '트럭'인 자동차의 대여 기록에 대해서
대여 기록 별로 대여 금액(컬럼명: FEE)을 구하여 대여 기록 ID와 대여 금액 리스트를 출력하는 SQL문을 작성 - 결과는 대여 금액을 기준으로 내림차순 정렬하고, 대여 금액이 같은 경우 대여 기록 ID를 기준으로 내림차순 정렬
- FEE의 경우 정수부분만 출력되어야 함
정답 코드
WITH TRUCK AS (SELECT CAR_ID, DAILY_FEE
FROM CAR_RENTAL_COMPANY_CAR
WHERE CAR_TYPE = '트럭'),
DISCOUNT_PLAN AS (SELECT CAST(SUBSTRING_INDEX(DURATION_TYPE, '일', 1) AS UNSIGNED) AS MIN_DAYS,
DISCOUNT_RATE
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE CAR_TYPE = '트럭'),
RENT_DAYS AS (SELECT HISTORY_ID,
CAR_ID,
DATEDIFF(END_DATE, START_DATE) + 1 AS RENTAL_DAYS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY),
BEST_DISCOUNT AS (SELECT R.HISTORY_ID,
R.CAR_ID,
T.DAILY_FEE,
R.RENTAL_DAYS,
P.DISCOUNT_RATE,
ROW_NUMBER() OVER (PARTITION BY R.HISTORY_ID
ORDER BY P.MIN_DAYS DESC) AS RN
FROM RENT_DAYS R JOIN TRUCK T ON R.CAR_ID = T.CAR_ID
LEFT JOIN DISCOUNT_PLAN P ON R.RENTAL_DAYS >= P.MIN_DAYS)
SELECT HISTORY_ID,
FLOOR(DAILY_FEE * RENTAL_DAYS * (1 - COALESCE(DISCOUNT_RATE, 0) / 100)) AS FEE
FROM BEST_DISCOUNT
WHERE RN = 1
ORDER BY FEE DESC, HISTORY_ID DESC
코드 설명
1. 'TRUCK' CTE
→ 불필요한 차량 제거
WITH TRUCK AS (SELECT CAR_ID, DAILY_FEE
FROM CAR_RENTAL_COMPANY_CAR
WHERE CAR_TYPE = '트럭'),
- 차량 테이블에서 '트럭'만 필터링
- 필요한 칼럼 CAR_ID, DAILY_FEE (일일 요금)만 선택
2. 'DISCOUNT_PLAN' CTE
→ 숫자 비교 가능한 형식으로 변환해 나중에 대여일과 비교하기 위함
DISCOUNT_PLAN AS (SELECT CAST(SUBSTRING_INDEX(DURATION_TYPE, '일', 1) AS UNSIGNED) AS MIN_DAYS,
DISCOUNT_RATE
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE CAR_TYPE = '트럭'),
- 할인 정책 중 CAR_TYPE = '트럭'만 필터링
- DURATION_TYPE은 문자열(예: "7일 이상")이므로, SUBSTRING_INDEX로 일 앞 숫자 추출
- 이 숫자를 UNSIGNED INT로 바꿔 MIN_DAYS로 저장
3. 'RENT_DAYS' CTE
→ 대여 기간에 따라 적절한 할인율을 찾기 위함
RENT_DAYS AS (SELECT HISTORY_ID,
CAR_ID,
DATEDIFF(END_DATE, START_DATE) + 1 AS RENTAL_DAYS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY),
- 대여 이력 테이블에서 렌트 일수를 계산
- DATEDIFF는 종료일 - 시작일이므로, 하루짜리 렌트도 포함되도록 +1
4. 'BEST_DISCOUNT' CTE
→ 대여일에 가장 적절한 할인율 '하나만' 선택하기 위함
BEST_DISCOUNT AS (SELECT R.HISTORY_ID,
R.CAR_ID,
T.DAILY_FEE,
R.RENTAL_DAYS,
P.DISCOUNT_RATE,
ROW_NUMBER() OVER (PARTITION BY R.HISTORY_ID
ORDER BY P.MIN_DAYS DESC) AS RN
FROM RENT_DAYS R JOIN TRUCK T ON R.CAR_ID = T.CAR_ID
LEFT JOIN DISCOUNT_PLAN P ON R.RENTAL_DAYS >= P.MIN_DAYS)
- RENT_DAYS와 TRUCK을 조인해서 대여 정보에 일일 요금 결합
- LEFT JOIN으로 RENTAL_DAYS >= MIN_DAYS 조건에 맞는 할인율만 연결
- 즉, 대여 기간이 할인 조건을 만족하면 해당 할인 연결
- ROW_NUMBER() 사용해, 같은 이력에 대해 가장 큰 MIN_DAYS (최우선 할인)을 RN = 1로 매김
5. 최종 SELECT 구문
SELECT HISTORY_ID,
FLOOR(DAILY_FEE * RENTAL_DAYS * (1 - COALESCE(DISCOUNT_RATE, 0) / 100)) AS FEE
FROM BEST_DISCOUNT
WHERE RN = 1
ORDER BY FEE DESC, HISTORY_ID DESC
- 할인율은 0일 수도 있으니 COALESCE(DISCOUNT_RATE, 0)로 처리
- 전체 요금 = 일일요금 × 대여일수 × (1 - 할인율)
- FLOOR()는 소수점 아래 버림 (정수 요금 처리)
- RN = 1 조건으로 각 렌트 이력마다 가장 적합한 할인만 반영
- 정렬 기준:
- 요금 높은 순
- 요금 동일 시, 이력 번호(HISTORY_ID)가 큰 순
함수 설명
SUBSTRING_INDEX() 함수
- 기본 문법
SUBSTRING_INDEX(str, delimiter, count)
- 문자열 str에서 delimiter(구분자)를 기준으로 count번째 앞부분까지 잘라냄
- count > 0: 왼쪽부터 자름
- count < 0: 오른쪽부터 자름
CAST(... AS UNSIGNED)
- 기본 문법
CAST(expression AS UNSIGNED)
- expression을 정수형 숫자 (양의 정수) 로 바꾸기
- 문자열에서 숫자만 있는 부분을 변환할 때 유용
ROW_NUMBER() 함수
ROW_NUMBER() OVER (PARTITION BY [컬럼]
ORDER BY [컬럼] [정렬방향]) AS [별칭]
- 각 행에 대해 고유한 순번 생성
- 순번은 PARTITION BY로 나눈 그룹 안에서, ORDER BY 기준으로 매겨짐
- 순번은 1부터 시작하며, 그룹마다 다시 1부터 시작
COALESCE()
COALESCE(value1, value2, ..., valueN)
- 인자들을 왼쪽에서 오른쪽으로 차례대로 검사해서 NULL이 아닌 첫 번째 값을 반환
- 만약 모든 인자가 NULL이면, 결과도 NULL
FLOOR()
FLOOR(number)
- 입력한 숫자보다 작거나 같은 정수 중 가장 큰 정수를 반환
- number : 실수형 숫자 입력값
- 즉, 소수점 아래를 내림 처리
첫 번째 시도 (실패)
WITH TRUCK AS (SELECT C.CAR_ID, C.CAR_TYPE, C.DAILY_FEE, D.DURATION_TYPE, D.DISCOUNT_RATE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE = '트럭'),
RENT_DAYS AS (SELECT HISTORY_ID, CAR_ID, (DATEDIFF(END_DATE, START_DATE)+1) AS DAYS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY)
SELECT R.HISTORY_ID, FLOOR((T.DAILY_FEE * (1 - T.DISCOUNT_RATE / 100.0)) * R.DAYS) AS FEE
FROM TRUCK T JOIN RENT_DAYS R ON T.CAR_ID = R.CAR_ID
WHERE SUBSTRING_INDEX(T.DURATION_TYPE, '일', 1) = R.DAYS
ORDER BY FEE DESC, R.HISTORY_ID DESC
문제점
- SUBSTRING_INDEX(T.DURATION_TYPE, '일', 1) = R.DAYS 조건
- DURATION_TYPE에는 "7일 이상", "30일 이상", "90일 이상" 과 같은 문자열이 들어 있는 상황
- SUBSTRING_INDEX(..., '일', 1)은 문자열 반환이므로 R.DAYS와 비교하려면 정수형 변환이 필요
- 또한 "이상"이기 때문에 >= 비교를 해야 하고, 최대 할인율을 찾아야 함
두 번째 시도 (실패)
WITH TRUCK AS (SELECT C.CAR_ID, C.CAR_TYPE, C.DAILY_FEE, D.DURATION_TYPE, D.DISCOUNT_RATE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE = '트럭'),
RENT_DAYS AS (SELECT HISTORY_ID, CAR_ID, (DATEDIFF(END_DATE, START_DATE)+1) AS DAYS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY),
DISCOUNT_INFO AS (SELECT CAR_TYPE,
CAST(SUBSTRING_INDEX(DURATION_TYPE, '일', 1) AS UNSIGNED) AS MIN_DAYS,
DISCOUNT_RATE
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN)
SELECT R.HISTORY_ID, FLOOR((T.DAILY_FEE * (1 - T.DISCOUNT_RATE / 100.0)) * R.DAYS) AS FEE
FROM TRUCK T JOIN RENT_DAYS R ON T.CAR_ID = R.CAR_ID
JOIN DISCOUNT_INFO I ON T.CAR_TYPE = I.CAR_TYPE
WHERE I.MIN_DAYS <= R.DAYS
ORDER BY FEE DESC, R.HISTORY_ID DESC
문제점
- 할인 정책이 중복 적용되는 문제 발생 가능성
- I.MIN_DAYS <= R.DAYS 조건만 있으면, 한 대여 기간에 대해 여러 할인 조건이 매칭될 수 있기 때문
이번 문제 왜이렇게 어렵게 느껴지는거지...? 다음에 한 번 더 풀어보자
'코딩 테스트 연습 > [프로그래머스][리트코드] MySQL' 카테고리의 다른 글
| 1757. Recyclable and Low Fat Products (2) | 2025.07.30 |
|---|---|
| [LV.5] JOIN > 상품을 구매한 회원 비율 구하기 (4) | 2025.07.28 |
| [LV.5] JOIN > 특정 기간동안 대여 가능한 자동차들의 대여비용 구하기 ⭐️ (2) | 2025.07.22 |
| [LV.5] GROUP BY > 입양 시각 구하기(2) ⭐️ (1) | 2025.07.18 |
| [LV.5] SELECT > 조건에 부합하는 중고거래 댓글 조회하기 (1) | 2025.07.16 |