세단 또는 SUVFEE DESC, CAR_TYPR ASC, CAR_ID DESCSELECT
C.CAR_ID,
C.CAR_TYPE,
FLOOR(C.DAILY_FEE * 30 * (100 - D.DISCOUNT_RATE) / 100) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C
JOIN CAR_RENTAL_COMPANY_RENTAL_HISTORY H
ON C.CAR_ID = H.CAR_ID
JOIN CAR_RENTAL_COMPANY_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 CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30' AND END_DATE >= '2022-11-01'
)
AND FLOOR(C.DAILY_FEE * 30 * (100 - D.DISCOUNT_RATE) / 100) BETWEEN 500000 AND 1999999
왜 오답이었을까?
1. JOIN CAR_RENTAL_COMPANY_RENTAL_HISTORY H를 하고 있음에도 불구하고
-> C.CAR_ID NOT IN (...)으로 다시 같은 테이블을 서브쿼리에서 사용
-> 이중으로 -RENTAL_HISTORY 테이블을 처리하고 있음
2022년 11월에 이미 대여된 차량도 결과에 섞일 수 있음
-> 왜? H 테이블에서 조인했기 때문에 대여 이력이 있는 차량들만 조회됨.
그러나 문제에서 찾아야 할 차량은 11월에 대여 이력이 없는 차량
D.DURATION_TYPE = '30일 이상' 조건이 빠짐.
-> CAR_RENTAL_COMPANY_DISCOUNT_PLAN 에는 여러 할인 기간별 요금 정책이 존재함.
문제에서 30일간 대여 요금을 구해야 하므로, 7일 이상이나 90일 이상과 같은 이상한 조건이 포함되지 않도록 조건을 걸어줘야 한다.
테이블이 3개 주어졌다고 무턱대고 JOIN부터 시작하지 말자.
이번에는 JOIN H ON C.CAR_ID = H.CAR_ID가 불필요한 것 같다.
결과가 이상하다면 불필요한 JOIN이 원인일 가능성이 있음을 알았다.
또, D.DURATION_TYPE = '30일 이상' 조건은 WHERE 절에 넣어야 하는 줄 알았는데 JOIN절에 넣을 수 있었다.
JOIN DISCOUNT_PLAN D
ON C.CAR_TYPE = D.CAR_TYPE
AND D.DURATION_TYPE = '30일 이상'
이 경우 JOIN 조건에 D.DURATION_TYPE = '30일 이상'이 포함되어 있어서 애초에 '30일 이상'인 행만 JOIN 대상이 된다. 필요한 데이터만 JOIN에서 불필요한 행을 줄일 수 있다.
SELECT
C.CAR_ID,
C.CAR_TYPE,
FLOOR(C.DAILY_FEE * 30 * (100 - D.DISCOUNT_RATE) / 100) AS FEE
FROM
CAR_RENTAL_COMPANY_CAR C
JOIN
CAR_RENTAL_COMPANY_DISCOUNT_PLAN D
ON C.CAR_TYPE = D.CAR_TYPE AND D.DURATION_TYPE = '30일 이상'
WHERE
C.CAR_TYPE IN ('세단', 'SUV')
AND C.CAR_ID NOT IN (
SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-11-30'
AND END_DATE >= '2022-11-01'
)
AND FLOOR(C.DAILY_FEE * 30 * (100 - D.DISCOUNT_RATE) / 100) BETWEEN 500000 AND 1999999
ORDER BY
FEE DESC,
C.CAR_TYPE ASC,
C.CAR_ID DESC;
저번에 DATEDIFF를 이용해 기간을 구할 때는 1을 더해야 했던 기억이 있다.
문제 1과 같이 테이블은 3개이다.
요구사항은;
'트럭'만HISTORY_ID) 별로FEE 계산:FLOOR(DAILY_FEE * 일수 * (100 - 할인율) / 100)HISTORY_ID, FEESELECT
H.HISTORY_ID,
FLOOR(C.DAILY_FEE * (DATEDIFF(H.END_DATE, H.START_DATE) + 1)
* (100 - COALESCE(D.DISCOUNT_RATE, 0)) / 100) AS FEE
FROM
CAR_RENTAL_COMPANY_RENTAL_HISTORY H
JOIN
CAR_RENTAL_COMPANY_CAR C
ON H.CAR_ID = C.CAR_ID
LEFT JOIN
CAR_RENTAL_COMPANY_DISCOUNT_PLAN D
ON C.CAR_TYPE = D.CAR_TYPE
AND D.DURATION_TYPE = (
CASE
WHEN DATEDIFF(H.END_DATE, H.START_DATE) + 1 >= 90 THEN '90일 이상'
WHEN DATEDIFF(H.END_DATE, H.START_DATE) + 1 >= 30 THEN '30일 이상'
WHEN DATEDIFF(H.END_DATE, H.START_DATE) + 1 >= 7 THEN '7일 이상'
ELSE NULL
END
)
WHERE
C.CAR_TYPE = '트럭'
ORDER BY
FEE DESC,
H.HISTORY_ID DESC;
처음에 COALESCE를 넣지 않아서 오답이 나왔다.
할인율이 없으면 0%로 처리해 줘야 하기 때문에 할인율을 COALESCE(D.DISCOUNT_RATE, 0)으로 작성해야 한다.
DATEDIFF(H.END_DATE, H.START_DATE) +1은 시작일을 포함하여 대여기간을 구한 것.
(* END_DATE를 먼저 써야 음수가 나오지 않는다)
우선 파이썬에서 수학 문제를 풀려면 그 개념을 정확히 알아야 한다.
최소공배수는 두 수의 곱 // 최대공약수 이다.
그런데 최대공약수는 도대체 어떻게 구하는지 알 수 없었다.
알아보니 유클리드 호제법이라는 알고리즘이 있었다.
✅ 유클리드 호제법이란?
"a를 b로 나눈 나머지를 r이라 하면, GCD(a, b) = GCD(b, r) 이다"
→ 이 과정을 반복하다가 나머지가 0이 되면, 그때의 b가 최대공약수
기본적으로 while을 이용하고, 반복 조건은 y가 0이 아닌 동안이 될 것이다.
함수 안에 함수 두 개를 또 정의하는 게 문제가 없음을 깨달았다.
def solution(n, m):
def gcd(x, y):
while y != 0:
x, y = y, x % y
return x
def lcm(x, y):
return x * y // gcd(x, y)
return [gcd(n, m), lcm(n, m)]
사실 while y != 0:을 while y:로 써도 된다고 한다.
예전에 읽은 아티클에서 코드를 가독성 좋게 짜는 사람은 while True:를 먼저 쓴다고 한 것 같기도 하다.
기본적으로 while 조건:은 조건이 True일 동안 반복된다. 숫자에서 0은 False, 그 외는 True로 취급된다.
결국 while y: 만으로도 'y가 0이 아닐 때까지 반복해라'라는 뜻이 되는 것이다.
3진법은 3을 밑으로 하는 기수법이다. 예를 들어 3진법 1200을 10진법으로 바꾸면;
(1 3^3) + (2 3^2) + (0 3^1) + (0 3^0) = 45가 된다.
결국 3으로 나눈 나머지를 생각하면 되겠다.
def solution(n):
answer = [] # ① 뒤집힌 3진법 자리들을 담을 리스트
while n: # ② n이 0이 될 때까지 반복
answer.append(str(n % 3)) # ③ 3으로 나눈 나머지를 문자열로 리스트에 추가
n = n // 3 # ④ n을 3으로 나눠 몫만 남김 (다음 자리 계산용)
return int(''.join(answer), 3) # ⑤ 리스트를 문자열로 합쳐서, 3진수로 보고 10진수로 바꿈
처음부터 3진법으로 변환할 때 뒤집힌 채로 리스트에 저장이 되는 방법이다.
그리고 int(숫자문자열, N진법) 은 문자열로 된 숫자를 N진법으로 해석해서 10진수로 변환하는 문법이라고 한다.
위 답안은 처음부터 문자열을 뒤집은 상태로 저장했지만, 사실 문제의 요구사항은 3진법으로 먼저 만들고 그 다음에 뒤집으라는 것이었다.
오히려 이게 더 간단할 수도 있겠다.
꼭 리스트를 만들 필요도 없다.
def solution(n):
answer = ''
while n:
answer = str(n % 3) + answer
n //= 3
reversed_answer = answer[::-1]
return int(reversed_answer, 3)
문자열을 뒤집을 때는 [::-1], 그리고 ''.join(reversed(_))가 많이 쓰인다고 한다.
다만 reversed()는 문자열을 거꾸로 순회하는 이터레이터이므로 ''.join(...)으로 다시 문자열로 합쳐줘야 하는 번거로움이 있어 보인다.
또 한 가지, n = n // 3은 축약하여 n //= 3으로 나타낼 수 있다.