
문제
https://school.programmers.co.kr/learn/courses/30/lessons/157339
프로그래머스
SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
문제 설명
- CAR_RENTAL_COMPANY_CAR 테이블과 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블과
CAR_RENTAL_COMPANY_DISCOUNT_PLAN 테이블에서 자동차 종류가 '세단' 또는 'SUV' 인 자동차 중
2022년 11월 1일부터 2022년 11월 30일까지 대여 가능하고 30일간의 대여 금액이 50만원 이상 200만원 미만인 자동차에 대해서
자동차 ID, 자동차 종류, 대여 금액(컬럼명: FEE) 리스트를 출력하는 SQL문을 작성 - 결과는 대여 금액을 기준으로 내림차순 정렬, 대여 금액이 같은 경우 자동차 종류를 기준으로 오름차순 정렬, 자동차 종류까지 같은 경우 자동차 ID를 기준으로 내림차순 정렬
정답 코드
WITH DISCOUNT_PLAN AS (SELECT *
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE DURATION_TYPE = '30일 이상'),
RENTED_NOV AS (SELECT DISTINCT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30' AND END_DATE >= '2022-11-01'),
FEE_CALC AS (SELECT C.CAR_ID,
C.CAR_TYPE,
FLOOR((C.DAILY_FEE * (1 - D.DISCOUNT_RATE / 100.0)) * 30) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C JOIN DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE IN ('세단', 'SUV'))
SELECT F.CAR_ID, F.CAR_TYPE, F.FEE
FROM FEE_CALC F
WHERE NOT EXISTS (SELECT 1
FROM RENTED_NOV R
WHERE R.CAR_ID = F.CAR_ID)
AND F.FEE BETWEEN 500000 AND 1999999
ORDER BY F.FEE DESC, F.CAR_TYPE, F.CAR_ID DESC
코드 설명
1. 'DISCOUNT_PLAN' CTE
WITH DISCOUNT_PLAN AS (
SELECT *
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE DURATION_TYPE = '30일 이상'
)
- CAR_RENTAL_COMPANY_DISCOUNT_PLAN 테이블에서
- 대여 기간이 30일 이상인 할인 계획만 골라서 임시 테이블(CTE)로 만들기
- 이후 할인율 정보를 이 테이블에서 가져올 예정
2. 'RENTED_NOV' CTE
RENTED_NOV AS (
SELECT DISTINCT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30' AND END_DATE >= '2022-11-01'
)
- CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서
- 2022년 11월 기간(11월 1일부터 11월 30일까지)과 겹치는 대여 기록이 있는 자동차 ID를 중복 없이 추출
- 즉, 11월에 대여된 차량 목록
3. 'FEE_CALC' CTE
FEE_CALC AS (
SELECT C.CAR_ID,
C.CAR_TYPE,
FLOOR((C.DAILY_FEE * (1 - D.DISCOUNT_RATE / 100.0)) * 30) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C JOIN DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE IN ('세단', 'SUV')
)
- CAR_RENTAL_COMPANY_CAR에서 세단과 SUV 차량을 대상으로
- 각 차량의 할인 적용된 일일 요금을 구함:
- C.DAILY_FEE * (1 - D.DISCOUNT_RATE / 100.0) → 할인 적용 후 일일 요금
- 여기에 30일을 곱해 한 달(30일 기준) 요금 계산
- FLOOR 함수로 소수점 내림 처리
- 할인 플랜과 차량 유형을 기준으로 조인해서 할인율을 적용
4. 메인 쿼리
SELECT F.CAR_ID, F.CAR_TYPE, F.FEE
FROM FEE_CALC F
WHERE NOT EXISTS (
SELECT 1
FROM RENTED_NOV R
WHERE R.CAR_ID = F.CAR_ID
)
AND F.FEE BETWEEN 500000 AND 1999999
ORDER BY F.FEE DESC, F.CAR_TYPE, F.CAR_ID DESC
* WHERE F.CAR_ID NOT IN (SELECT CAR_ID FROM RENTED_NOV) AND ~ 도 가능
- FEE_CALC에서 계산된 차량별 월 요금 정보를 사용
- NOT EXISTS 조건으로 11월에 대여된 차량은 제외
- 즉, RENTED_NOV에 포함되지 않은 차량만 선택
- 월 요금이 50만 이상, 200만 미만인 차량만 필터링
- 결과를 월 요금 내림차순, 차량 종류 오름차순, 차량 ID 내림차순으로 정렬하여 출력
NOT EXISTS 기본 구조
SELECT 컬럼들
FROM 메인테이블 M
WHERE NOT EXISTS (
SELECT 1
FROM 서브테이블 S
WHERE S.조건 = M.조건
)
- 메인테이블 M의 각 행에 대해
서브쿼리에서 M과 관련된 조건을 만족하는 행이 존재하지 않을 때만 결과에 포함 - 왜 SELECT 1을 쓰는가?
- SELECT 1은 존재 여부만 확인하기 위한 간단한 표현
- EXISTS나 NOT EXISTS 서브w쿼리 안에서 실제로 어떤 컬럼을 선택하느냐는 결과에 영향이 없음
첫 번째 시도 (실패)
WITH DISCOUNT_PLAN AS (SELECT *
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE DURATION_TYPE = '30일 이상')
SELECT C.CAR_ID, C.CAR_TYPE, FLOOR((C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN CAR_RENTAL_COMPANY_RENTAL_HISTORY H ON C.CAR_ID = H.CAR_ID
JOIN DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE IN ('세단', 'SUV') AND
(H.START_DATE < '2022-11-01' OR H.START_DATE > '2022-11-30') AND
(H.END_DATE < '2022-11-01' OR H.END_DATE > '2022-11-30') AND
500000 <= (C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 AND
(C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 < 2000000
ORDER BY FEE DESC, C.CAR_TYPE, C.CAR_ID DESC
문제점
- 자동차가 2022년 11월 전체에 '대여 불가'한 경우를 제외해야 하는데, 지금 조건은 '1건이라도 대여 기록이 11월과 겹치지 않으면 포함'하고 있음
- 중복 대여 기록으로 인해 JOIN 시 자동차 한 대가 여러 번 나올 수 있음
두 번째 시도 (실패)
WITH DISCOUNT_PLAN AS (SELECT *
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE DURATION_TYPE = '30일 이상'),
RENTED_NOV AS (SELECT DISTINCT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30' AND END_DATE >= '2022-11-01')
SELECT C.CAR_ID, C.CAR_TYPE, FLOOR((C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN RENTED_NOV R ON C.CAR_ID = R.CAR_ID
JOIN DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE IN ('세단', 'SUV') AND
C.CAR_ID NOT IN (SELECT CAR_ID FROM RENTED_NOV) AND
500000 <= (C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 AND
(C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 < 2000000
GROUP BY C.CAR_ID, C.CAR_TYPE
ORDER BY FEE DESC, C.CAR_TYPE, C.CAR_ID DESC
- 중복 발생할 일이 없는, 지금 상황에서는 GROUP BY 필요 없음
문제점
JOIN RENTED_NOV R ON C.CAR_ID = R.CAR_ID
...
WHERE C.CAR_ID NOT IN (SELECT CAR_ID FROM RENTED_NOV)
- 같은 테이블을 JOIN 해서 포함시켜놓고, 다시 WHERE에서 그 값을 제외하는 구조
→ 자기모순 (논리 충돌) 발생
세 번째 시도 (성공. 개선 필요)
WITH DISCOUNT_PLAN AS (SELECT *
FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
WHERE DURATION_TYPE = '30일 이상'),
RENTED_NOV AS (SELECT DISTINCT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30' AND END_DATE >= '2022-11-01')
SELECT C.CAR_ID, C.CAR_TYPE, FLOOR((C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN DISCOUNT_PLAN D ON C.CAR_TYPE = D.CAR_TYPE
WHERE C.CAR_TYPE IN ('세단', 'SUV') AND
C.CAR_ID NOT IN (SELECT CAR_ID FROM RENTED_NOV) AND
500000 <= (C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 AND
(C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 < 2000000
ORDER BY FEE DESC, C.CAR_TYPE, C.CAR_ID DESC
개선점
- (C.DAILY_FEE * (1-D.DISCOUNT_RATE/100)) * 30 → 중복 계산 줄이기
- NOT IN은 NULL 때문에 예외가 발생할 수 있어, 더 안전한 NOT EXISTS 사용 권장
- DISCOUNT_RATE가 실수일 가능성을 대비해 '1 - (D.DISCOUNT_RATE / 100.0)'처럼 형변환을 명시하면 계산 오류 방지
'코딩 테스트 연습 > [프로그래머스][리트코드] MySQL' 카테고리의 다른 글
| [LV.5] JOIN > 상품을 구매한 회원 비율 구하기 (4) | 2025.07.28 |
|---|---|
| [LV.5] String, Date > 자동차 대여 기록 별 대여 금액 구하기 ⭐️ (5) | 2025.07.24 |
| [LV.5] GROUP BY > 입양 시각 구하기(2) ⭐️ (1) | 2025.07.18 |
| [LV.5] SELECT > 조건에 부합하는 중고거래 댓글 조회하기 (1) | 2025.07.16 |
| [LV.5] SELECT > 오프라인/온라인 판매 데이터 통합하기 ⭐️ (2) | 2025.07.14 |