이번 문제는 단순한 JOIN으로 풀리는 문제가 아니었다.
문제에서 요구하는 조건이 꼼꼼하고, "부분 겹침", "전체 탈락" 같은 실무에서 자주 나오는 패턴이 포함되어 있었다.
또한 JOIN ON 절과 WHERE 절의 구분, NOT EXISTS의 필요성, 날짜 겹침 판단 등 여러 개념을 동시에 이해해야 정확하게 풀 수 있는 문제였다.
1️⃣ 자동차 종류는 '세단', 'SUV' 만 조회
2️⃣ 2022년 11월 1일 ~ 11월 30일 사이에 대여 기록이 없는 차량만 조회해야 한다
3️⃣ 30일 이상 할인 적용 후 요금 계산
4️⃣ 할인 적용 후 30일 요금이 50만원 이상 200만원 미만인 차량만 조회
5️⃣ 정렬 조건: FEE 내림차순 → CAR_TYPE 오름차순 → CAR_ID 내림차순
SELECT
C.CAR_ID,
C.CAR_TYPE,
FLOOR(C.DAILY_FEE * 30 * (1 - P.DISCOUNT_RATE / 100)) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN P
ON C.CAR_TYPE = P.CAR_TYPE
AND P.DURATION_TYPE = '30일 이상'
WHERE C.CAR_TYPE IN ('세단', 'SUV')
AND NOT EXISTS (
SELECT 1
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY H
WHERE H.CAR_ID = C.CAR_ID
AND H.START_DATE <= '2022-11-30'
AND H.END_DATE >= '2022-11-01'
)
AND C.DAILY_FEE * 30 * (1 - P.DISCOUNT_RATE / 100) BETWEEN 500000 AND 1999999
ORDER BY FEE DESC, C.CAR_TYPE ASC, C.CAR_ID DESC;
문제는 "11월에 겹치는 대여 기록이 하나라도 있으면 그 차량은 탈락" 조건이다.
처음에는 LEFT JOIN + WHERE OR 조건으로 시도했었다:
LEFT JOIN RENTAL_HISTORY H ON C.CAR_ID = H.CAR_ID
WHERE H.CAR_ID IS NULL OR (H.START_DATE > '2022-11-30' OR H.END_DATE < '2022-11-01')
하지만 이 방법은 row 단위로 조건을 평가한다.
→ 차량(CAR_ID)이 여러 대여 기록을 가지고 있을 때,
→ 일부 row는 조건을 만족하고 일부 row는 만족하지 않으면 → 조건 만족한 row가 남아서 차량이 살아남아버린다.
| CAR_ID | START_DATE | END_DATE |
|---|---|---|
| 1 | 2022-10-01 | 2022-10-10 |
| 1 | 2022-11-05 | 2022-11-10 |
| 1 | 2022-12-01 | 2022-12-10 |
→ 1번 차량은 11월에 겹치는 기록이 있음(11/5 ~ 11/10).
→ 하지만 다른 row가 조건을 만족해서 → 결과에 포함되는 문제가 발생한다.
→ 그러므로 row 단위 평가가 아닌 → 차량 단위로 EXISTS 여부를 체크할 수 있는 NOT EXISTS가 필요하다.
AND NOT EXISTS (
SELECT 1
FROM RENTAL_HISTORY H
WHERE H.CAR_ID = C.CAR_ID
AND H.START_DATE <= '2022-11-30'
AND H.END_DATE >= '2022-11-01'
)
WHERE H.CAR_ID = C.CAR_ID → 현재 차량에 대한 RENTAL_HISTORY 기록을 검사한다.WHERE H.CAR_ID = C.CAR_ID → 바로 이 부분이 기준이다.
각 차량(CAR_ID)에 대해 서브쿼리를 실행하고,
겹치는 기록이 존재하면 SELECT 결과에서 아예 제외된다 (DELETE가 아님, 출력에서 제외되는 것).
조건:
H.START_DATE <= '2022-11-30'
AND H.END_DATE >= '2022-11-01'
왜 이렇게 써야 하는가?
| START_DATE | END_DATE | 겹치는지 여부 |
|---|---|---|
| 2022-10-25 | 2022-11-05 | 겹침 |
| 2022-11-10 | 2022-12-01 | 겹침 |
| 2022-10-01 | 2022-10-31 | 겹치지 않음 |
→ 만약 "정확히 11월 1일 ~ 30일 전체 포함" 조건으로 쓰면 부분 겹침을 잡을 수 없게 된다.
→ 시작일이 11월 30일 이전이고 종료일이 11월 1일 이후라면 → 11월과 겹친다고 판단해야 정확하다.
많은 사람들이 처음에 혼동하는 부분이다.
이번 문제에서도 처음에 "왜 어떤 조건은 JOIN 뒤에 AND로 쓰고, 어떤 조건은 WHERE 절에 쓰는지"가 궁금했다.
| 목적 | 사용하는 위치 |
|---|---|
| 두 테이블을 "어떻게 연결할 것인가" | JOIN ON 절 |
| 조인 후 결과 row에서 "어떤 것만 남길 것인가" | WHERE 절 |
WHERE C.CAR_TYPE IN ('세단', 'SUV')
→ 이 조건은 테이블 간 연결 조건이 아니고,
→ "최종 결과에서 어떤 자동차만 보여줄 것인가" 라는 필터링 조건이다.
→ 따라서 WHERE 절에 쓰는 것이 맞다.
이 질문도 아주 중요한 포인트였다.
JOIN P ON C.CAR_TYPE = P.CAR_TYPE
AND P.DURATION_TYPE = '30일 이상'
→ 이 조건은 "조인할 때부터 '30일 이상' 할인 정책만 JOIN" 하고 싶어서 ON 절에 쓴 것이다.
→ 만약 WHERE 절에 쓴다면:
JOIN P ON C.CAR_TYPE = P.CAR_TYPE
WHERE P.DURATION_TYPE = '30일 이상'
이렇게 되면:
| 조건의 목적 | 쓰는 위치 |
|---|---|
| 두 테이블을 "어떻게 연결할 것인가" | JOIN ON |
| 결과 row 중 "어떤 것만 남길 것인가" | WHERE |
처음에는:
LEFT JOIN RENTAL_HISTORY H ON C.CAR_ID = H.CAR_ID
을 사용했지만, NOT EXISTS 서브쿼리에서 이미 RENTAL_HISTORY를 검사하고 있기 때문에
메인 쿼리에서는 LEFT JOIN을 사용할 필요가 없었다.
→ 불필요한 JOIN 제거 후 훨씬 깔끔하고 효율적이 된다.
이번 문제는 단순한 조인 문제처럼 보이지만,
"부분 겹침 처리", "전체 탈락 조건 구현", "JOIN과 WHERE 구분" 같은 실무에서 매우 중요한 패턴을 익힐 수 있는 문제였다.
이런 패턴은:
등에서 실제로 자주 사용된다.
이번 문제를 통해 이 패턴을 정확하게 익혀두면 다른 복잡한 SQL 문제에서도 매우 유용하게 활용할 수 있다.