1. Problem
세 가지 복합 조건을 동시에 충족하는 자동차를 찾아야 한다.
- 차종 조건: '세단' 또는 'SUV'일 것.
- 가용성 조건: 2022년 11월 1일부터 11월 30일까지 대여 기록이 전혀 겹치지 않을 것.
- 금액 조건: 30일간 대여 시 총비용(FEE)이 50만 원 이상 200만 원 미만일 것. 단순한 조인을 넘어 날짜의 '교집합'이 없는 집합을 추출해야 하는 고난도 문제이다.
2. Solution
복잡한 날짜 조건은 NOT IN 서브쿼리로 해결하고, 계산된 별칭(FEE)에 대한 필터링은 HAVING 절을 전략적으로 활용한다.
- 기간 겹침(Overlap) 로직: 기존 종료일 >= 신규 시작일 AND 기존 시작일 <= 신규 종료일 조건을 사용하면 조금이라도 기간이 겹치는 모든 행을 찾을 수 있다.
3. Takeaway (예약 시스템의 핵심 논리와 성능 최적화)
실제 서비스 로직을 SQL로 구현하며 분석가가 정립해야 할 객관적 설계 원칙을 정리한다.
- 여집합을 통한 가용성 판단: "11월 내내 비어 있는 차"를 직접 찾는 것보다 "11월에 하루라도 빌려진 차"를 찾아 전체에서 빼는 방식이 훨씬 견고한 로직을 보장한다.
- 조인 조건 내 필터링: DISCOUNT_PLAN 조인 시 ON 절에 DURATION_TYPE = '30일 이상'을 명시하여, 조인 후 행이 불필요하게 늘어나는 것을 방지하고 연산 대상 집합을 최소화했다.
- MySQL의 특수성 활용: WHERE 절은 집계 전 데이터를 거르고, HAVING 절은 집계 후(또는 SELECT 절의 계산 이후) 데이터를 거른다. 별칭(FEE)을 직접 필터링에 사용하는 방식은 쿼리의 가독성을 획기적으로 높여준다.
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 테이블 입니다.
CAR_RENTAL_COMPANY_CAR 테이블은 아래와 같은 구조로 되어있으며, CAR_ID, CAR_TYPE, DAILY_FEE, OPTIONS 는 각각 자동차 ID, 자동차 종류, 일일 대여 요금(원), 자동차 옵션 리스트를 나타냅니다.
| Column name | Type | Nullable |
| CAR_ID | INTEGER | FALSE |
| CAR_TYPE | VARCHAR(255) | FALSE |
| DAILY_FEE | INTEGER | FALSE |
| OPTIONS | VARCHAR(255) | FALSE |
자동차 종류는 '세단', 'SUV', '승합차', '트럭', '리무진' 이 있습니다. 자동차 옵션 리스트는 콤마(',')로 구분된 키워드 리스트(예: ''열선시트,스마트키,주차감지센서'')로 되어있으며, 키워드 종류는 '주차감지센서', '스마트키', '네비게이션', '통풍시트', '열선시트', '후방카메라', '가죽시트' 가 있습니다.
CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블은 아래와 같은 구조로 되어있으며, HISTORY_ID, CAR_ID, START_DATE, END_DATE 는 각각 자동차 대여 기록 ID, 자동차 ID, 대여 시작일, 대여 종료일을 나타냅니다.
| Column name | Type | Nullable |
| HISTORY_ID | INTEGER | FALSE |
| CAR_ID | INTEGER | FALSE |
| START_DATE | DATE | FALSE |
| END_DATE | DATE | FALSE |
CAR_RENTAL_COMPANY_DISCOUNT_PLAN 테이블은 아래와 같은 구조로 되어있으며, PLAN_ID, CAR_TYPE, DURATION_TYPE, DISCOUNT_RATE 는 각각 요금 할인 정책 ID, 자동차 종류, 대여 기간 종류, 할인율(%)을 나타냅니다.
| Column name | Type | Nullable |
| PLAN_ID | INTEGER | FALSE |
| CAR_TYPE | VARCHAR(255) | FALSE |
| DURATION_TYPE | VARCHAR(255) | FALSE |
| DISCOUNT_RATE | INTEGER | FALSE |
할인율이 적용되는 대여 기간 종류로는 '7일 이상' (대여 기간이 7일 이상 30일 미만인 경우), '30일 이상' (대여 기간이 30일 이상 90일 미만인 경우), '90일 이상' (대여 기간이 90일 이상인 경우) 이 있습니다. 대여 기간이 7일 미만인 경우 할인정책이 없습니다.
문제
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를 기준으로 내림차순 정렬해주세요.
예시
예를 들어 CAR_RENTAL_COMPANY_CAR 테이블과 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블과 CAR_RENTAL_COMPANY_DISCOUNT_PLAN 테이블이 다음과 같다면
| CAR_ID | CAR_TYPE | DAILY_FEE | OPTIONS |
| 1 | SUV | 25000 | 가죽시트,열선시트,후방카메라 |
| 2 | 세단 | 14000 | 스마트키,네비게이션,열선시트 |
| 3 | 트럭 | 32000 | 주차감지센서,후방카메라,가죽시트 |
| 4 | 세단 | 12000 | 열선시트,후방카메라 |
| 5 | 세단 | 22000 | 스마트키,주차감지센서 |
| HISTORY_ID | CAR_ID | START_DATE | END_DATE |
| 1 | 1 | 2022-08-27 | 2022-09-02 |
| 2 | 1 | 2022-10-03 | 2022-10-04 |
| 3 | 2 | 2022-10-05 | 2022-10-20 |
| 4 | 2 | 2022-10-10 | 2022-11-12 |
| 5 | 3 | 2022-10-16 | 2022-10-17 |
| PLAN_ID | CAR_TYPE | DURATION_TYPE | DISCOUNT_RATE |
| 1 | 트럭 | 7일 이상 | 5% |
| 2 | 트럭 | 30일 이상 | 7% |
| 3 | 트럭 | 90일 이상 | 10% |
| 4 | 세단 | 7일 이상 | 5% |
| 5 | 세단 | 30일 이상 | 10% |
| 6 | 세단 | 90일 이상 | 15% |
| 7 | SUV | 7일 이상 | 3% |
| 8 | SUV | 30일 이상 | 8% |
| 9 | SUV | 90일 이상 | 12% |
자동차 종류가 '세단' 또는 'SUV' 인 자동차 중 2022년 11월 1일 부터 2022년 11월 30일까지 대여가능한 자동차는 자동차 ID가 1, 4, 5인 자동차입니다.
일일 대여 요금에 자동차 종류 별 대여기간이 30일 이상인 경우의 할인율을 적용하여 30일간의 대여 금액을 구하면,
- 자동차 ID가 1인 경우, 일일 대여 금액 25,000원에서 8% 할인율을 적용하고 30일을 곱하면 총 대여 금액은 690,000원
- 자동차 ID가 4인 경우, 일일 대여 금액 12,000원에서 10% 할인율을 적용하고 30일을 곱하면 총 대여 금액은 324,000원
- 자동차 ID가 5인 경우, 일일 대여 금액 22,000원에서 10% 할인율을 적용하고 30일을 곱하면 총 대여 금액은 621,000원이고, 대여 금액이 50만원 이상 200만원 미만인 경우에 대해서 대여 금액을 기준으로 내림차순, 자동차 종류를 기준으로 오름차순 및 자동차 ID를 기준으로 내림차순 정렬하면 다음과 같아야 합니다.
| CAR_ID | CAR_TYPE | FEE |
| 5 | 세단 | 690000 |
| 1 | SUV | 621000 |
주의사항
FEE의 경우 예시처럼 정수부분만 출력되어야 합니다.
1. 정답 쿼리
SELECT C.CAR_ID,
C.CAR_TYPE,
ROUND((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') # 조건1
AND C.CAR_ID NOT IN (SELECT CAR_ID # 조건2
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE END_DATE >= '2022-11-01' AND START_DATE <= '2022-11-30')
HAVING FEE >= 500000 AND FEE < 2000000 # 조건3 select절의 alias 사용
ORDER BY FEE DESC, C.CAR_TYPE ASC, C.CAR_ID DESC;
찾고 싶은 것은 2022년 11월 1일부터 11월 30일까지 한 번도 빌려지지 않은(대여 가능한) 차입니다. 그러려면 반대로 그 기간에 조금이라도 걸쳐 있는 차를 찾아서 빼야 합니다.
1. 기간이 겹치는 논리적 조건 (Overlap)
두 기간(기록상의 대여 기간 vs 우리가 원하는 11월 한 달)이 겹치려면 다음 두 조건이 동시에 만족되어야 합니다.
- END_DATE >= '2022-11-01': 대여 종료일이 11월 1일 이후여야 합니다. (적어도 11월의 시작점보다는 늦게 끝나야 11월에 발을 걸칩니다.)
- START_DATE <= '2022-11-30': 대여 시작일이 11월 30일 이전이어야 합니다. (적어도 11월이 끝나기 전에는 대여가 시작되어야 11월에 발을 걸칩니다.)
"GROUP BY가 없어도 HAVING을 쓸 수 있나요?"
- 원칙적으로 HAVING은 그룹화된 데이터에 조건을 걸 때 쓴다.
- 하지만 MySQL에서는 SELECT 절에서 만든 계산 컬럼의 별칭을 필터링하고 싶을 때, 서브쿼리 없이 HAVING으로 편하게 접근할 수 있다.
- 즉, WHERE는 원래 테이블 컬럼에, HAVING은 계산된 별칭에 조건을 걸 때 유용하다!
출제자의 핵심 의도
1. "기간 겹침(Overlap)의 논리를 이해하고 있는가?"
이 문제의 가장 큰 함정은 "대여 가능"이라는 말을 보고 START_DATE나 END_DATE를 11월 범위 안으로 단순하게 비교하는 것입니다.
- 의도: "11월 한 달간 대여 가능"하려면 기존 예약 중 11월에 조금이라도 걸치는 모든 기록을 찾아내서 제외(NOT IN)해야 합니다.
- 검증: 복잡한 날짜 조건 속에서 NOT IN과 AND/OR 논리를 실수 없이 설계할 수 있는지 봅니다.
2. "다중 테이블 조인과 데이터 흐름 제어"
이 문제는 테이블 3개를 연결해야 합니다 (자동차, 대여 기록, 할인 정책).
- 의도: 각 테이블의 관계를 파악하고, 필요한 정보를 적재적소에 가져오는 능력을 봅니다.
- 포인트: 특히 할인 정책(DISCOUNT_PLAN)을 조인할 때, '30일 이상'이라는 특정 조건(DURATION_TYPE)만 골라내어 조인하는 조인 조건 최적화 능력을 확인합니다.
3. "가공된 데이터(계산 컬럼)에 대한 필터링 능력"
단순히 테이블에 있는 값을 뽑는 게 아니라, (일일 요금 * 30일 * 할인율)이라는 복잡한 계산을 거친 **'최종 금액(FEE)'**을 기준으로 필터링해야 합니다.
- 의도: 앞서 질문하신 것처럼 HAVING을 쓰든 인라인 뷰를 쓰든, SELECT 절에서 계산된 별칭을 WHERE 단계에서 어떻게 처리할지에 대한 해결 능력을 봅니다.
- 검증: FEE >= 500000 AND FEE < 2000000 같은 범위를 정확히 제한하는지 확인합니다.
4. "엣지 케이스(Edge Case) 처리 및 정렬 디테일"
문제 마지막에 정렬 조건이 3개나 붙어 있습니다 (금액 내림차순, 종류 오름차순, ID 내림차순).
- 의도: 실무 데이터는 중복되거나 비슷한 값이 많습니다. 이때 사용자가 원하는 정확한 순서로 데이터를 정렬해 줄 수 있는 꼼꼼함을 봅니다.
- 포인트: ROUND 함수를 써서 정수 부분만 출력하라는 조건 등, 요구사항 명세서를 얼마나 정확히 반영하는지(Compliance)를 평가합니다.
🚩면접 팁
"이 문제는 '예약 시스템'의 핵심 로직을 묻는 문제입니다."
만약 면접관이 "이 쿼리에서 가장 어려웠던 부분이 무엇인가요?"라고 묻는다면,
**"특정 기간에 대여가 불가능한 차량을 NOT IN과 기간 중복 논리를 이용해 완벽하게 필터링하는 부분이 가장 중요하다고 생각했습니다"**라고 답변해 보세요.
출제자의 의도를 정확히 파악했다는 인상을 줄 수 있습니다.
'Data Science > SQL' 카테고리의 다른 글
| [SQL] 비트연산&, 기간중복, 서브쿼리 Alias 규칙, EXISTS, HAVING (0) | 2026.02.15 |
|---|---|
| [SQL/오답] 기간별 할인 로직 구현; WITH 절과 LEFT JOIN의 조화 (프로그래머스 Lv4) (1) | 2026.02.15 |
| [SQL/오답] 비트 연산자의 다대다 매칭과 EXISTS 성능 최적화 (프로그래머스 Lv4) (0) | 2026.02.15 |
| [SQL/오답] 비트 연산으로 다대다 스킬 매칭: 비트 마스크와 인라인 뷰 (프로그래머스 Lv4) (1) | 2026.02.15 |
| [SQL/오답] Self Join 대장균 3대 가계도 추적: 세대 고정 논리 (프로그래머스 Lv4) (0) | 2026.02.14 |