컴퓨터 과학/[프로그래머스 & Leet Code] MySQL 문제 풀이

[programmers] JOIN

계란💕 2023. 5. 29. 04:04

5월 식품들의 총매출 조회하기

 출처 - https://school.programmers.co.kr/learn/courses/30/lessons/131117

  • 생산일자가 2022년 5월에 해당하는 식품의 ID, NAME, 총매출을 조회하는 SQL문을 작성
  • 정렬: 총매출 내림차순 정렬, 식품 ID 오름차순 정렬

 

  MySol)

<hide/>
SELECT PRODUCT_ID, PRODUCT_NAME, SUM(PRICE * AMOUNT) TOTAL_SALES
FROM FOOD_PRODUCT JOIN FOOD_ORDER USING (PRODUCT_ID)
WHERE YEAR(PRODUCE_DATE) = 2022 AND MONTH(PRODUCE_DATE) = 5
GROUP BY PRODUCT_ID
ORDER BY 3 DESC, 1

 


 주문량이 많은 아이스크림 조회하기 - level 4

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/133027

  • TEMP 테이블 안에서 정렬을 먼저하고 그 다음에 
  • 상위 3개만 골라서 FLAVOR만 출력한다.

  Sol)

<hide/>
WITH TEMP AS
(
SELECT FLAVOR, SUM(J.TOTAL_ORDER + FH.TOTAL_ORDER) 
FROM FIRST_HALF FH JOIN JULY J USING (FLAVOR)
GROUP BY FLAVOR
ORDER BY 2 DESC
)
SELECT  FLAVOR
FROM TEMP
LIMIT 3

 


그룹별 조건에 맞는 식당 목록 출력하기 - level 4

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/131124

 

  • 먼저 from 내부 쿼리를 이용해서  max_tmp 테이블에 최대 개수와 member_id를 추출한다. 
  • 그리고 해당  member_id와  문제의 테이블에서 id를 비교해서 회원 정보를 반환하도록 한다.  
<hide/>
WITH MAX_TMP AS
(
SELECT COUNT(REVIEW_ID) MAX_CNT, MP.MEMBER_ID MAX_MEMBER_ID, MEMBER_NAME
FROM MEMBER_PROFILE MP JOIN REST_REVIEW USING (MEMBER_ID) 
GROUP BY MP.MEMBER_ID
ORDER BY 1 DESC
LIMIT 1
)
SELECT MEMBER_NAME MEMBER_NAME, REVIEW_TEXT, DATE_FORMAT(REVIEW_DATE, '%Y-%m-%d') REVIEW_DATE
FROM MAX_TMP JOIN REST_REVIEW R ON MAX_MEMBER_ID = R.MEMBER_ID
ORDER BY 3, 2

 


없어진 기록 찾기 - level 3

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/59042

  • out 테이블에는 존재하지만  in 테이블에는 존재하지 않는 데이터만 출력하면 된다.
  • (ANIMAL_ID)를  가지고  사이에 두고 RIGHT JOIN을 한다. 
    • 그러면 OUT 테이블의 모든 튜플이 출력된다.
    • OUT  테이블에는 존재하지만 IN 테이블에는 존재하지 않는 튜플의 ANIMAL_ID에는 null이 들어간다.
    • 따라서 WHERE 절에 I.ANIMAL_ID 가 NULL인 경우만 출력해야한다. 
    • 그러면 (OUT 집합 원소) - (IN 집합과 OUT집합의 교집합 원소) 에 해당하는 값을 출력할 수 있다.

  Sol)

<hide/>
SELECT O.ANIMAL_ID, O.NAME
FROM ANIMAL_INS I  RIGHT JOIN ANIMAL_OUTS O USING (ANIMAL_ID)
WHERE I.ANIMAL_ID IS NULL
ORDER BY O.ANIMAL_ID

 

 


있었는데요 없었습니다 - level 3

 출처 - https://school.programmers.co.kr/learn/courses/30/lessons/59043

  • 관리자의 실수로 일부 동물의 입양일이 잘못 입력되었습니다. 보호 시작일보다 입양일이 더 빠른 동물의 아이디와 이름을 조회하는 SQL문을 작성해주세요. 이때 결과는 보호 시작일이 빠른 순으로 조회해야합니다.
  • 날짜도 숫자와 같이 부등호로 비교할 수 있다. 
  • where절에 조건을 넣어서 비교한다. 

  Sol) 

<hide/>
SELECT O.ANIMAL_ID, O.NAME
FROM ANIMAL_INS I   JOIN ANIMAL_OUTS O USING (ANIMAL_ID)
WHERE I.DATETIME > O.DATETIME
ORDER BY I.DATETIME

 


오랜 기간 보호한 동물(1) -  level 3

 출처 - https://school.programmers.co.kr/learn/courses/30/lessons/59044 

 

  • TIMESTAMPDIFF(DAY, 시작 날, 끝나는 날): 기간을 입력한 단위인 DAY 로 반환할 수 있다. 
  • CURTIME(): 현재 시간 (날짜, 시간 포함), (CURDATE()는 날짜까지만 반환)
  • COALESCE(DATETIME, CURTIME()): DATETIME가 NULL 인 경우,  CURTIME을 반환한다.

  Sol) 

<hide/>
WITH TMP AS
(
SELECT TIMESTAMPDIFF(DAY, I.DATETIME, COALESCE(O.DATETIME, CURDATE())) DIFF, 
     I.NAME, I.DATETIME 
FROM ANIMAL_INS I LEFT JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE I.ANIMAL_ID NOT IN (
                        SELECT ANIMAL_ID
                        FROM ANIMAL_OUTS
)
ORDER BY DIFF DESC
LIMIT 3
)
SELECT NAME, DATETIME
FROM TMP

 


보호소에서 중성화한 동물 -  level 4

 출처 -  https://school.programmers.co.kr/learn/courses/30/lessons/59045

  • WHERE 절 조건에 IN, LIKE 키워드를 넣을 수도 있지만 간단하게 중성화  여부가 바뀌었는지 아닌지의 여부만 비교했다. 

  Sol)

<hide/>
SELECT I.ANIMAL_ID, I.ANIMAL_TYPE, I.NAME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS USING (ANIMAL_ID)
WHERE SEX_UPON_INTAKE != SEX_UPON_OUTCOME

 


상품 별 오프라인 매출 구하기 - level 2

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/59042

  • 총 매출을 구해야하기 때문에 SUM() 을 이용한다.

  Sol) 

<hide/>
SELECT PRODUCT_CODE, SUM(PRICE * SALES_AMOUNT) "SALES"
FROM PRODUCT JOIN OFFLINE_SALE USING (PRODUCT_ID)
GROUP BY PRODUCT_CODE
ORDER BY 2 DESC, 1

 


조건에 맞는 도서과 저자 리스트 출력하기 - level 2

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/144854

 

  • date_format(date, '%y-%m-%d'): date를 원하는 형식으로 바꿀 수 있다.

  Sol)

<hide/>
SELECT BOOK_ID, AUTHOR_NAME, date_format(PUBLISHED_DATE, "%Y-%m-%d") PUBLISHED_DATE
FROM BOOK  JOIN AUTHOR  USING (AUTHOR_ID)
WHERE category = '경제'
ORDER BY 3

 

 


상품을 구매한 회원 비율 구하기 - level 5

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/131534

 

  Sol)

<hide/>
WITH
TB_회원가입 AS
(
    SELECT YEAR(JOINED) YEAR, MONTH(JOINED) MONTH, USER_ID
    FROM USER_INFO
    WHERE YEAR(JOINED) = 2021
),
TB_회원수 AS
(
    SELECT COUNT(USER_ID) 모든회원수
    FROM USER_INFO
    WHERE YEAR(JOINED) = 2021    
)
SELECT 
YEAR(SALES_DATE) YEAR, MONTH(SALES_DATE) MONTH, COUNT(DISTINCT USER_ID) PUCHASED_USERS, 
    ROUND(COUNT(DISTINCT USER_ID) / TB_회원수.모든회원수, 1)  PUCHASED_RATIO
FROM TB_회원가입
    JOIN ONLINE_SALE USING (USER_ID) 
    JOIN TB_회원수 
GROUP BY YEAR(SALES_DATE), MONTH(SALES_DATE)
ORDER BY YEAR(SALES_DATE), MONTH(SALES_DATE)
  • 푸는데 오래 걸려서 결국 GPT의 도움을 받았다.
  • 거의 정답에 근접했지만 DISTINCT 키워드를 생각하지 못해서 풀지 못했다. 
  • 2021년에 한해서 회원 정보와 가입연월 정보를 가져오고나서 마지막에 GTOUP BY, ORDER BY를 할 때에 구매 연월 정보가 필요하다. 
  •  문제 : (2021년 가입한 회원 중에서 상품을 구매한 회원 수) / (2021년 가입 회원 수)
    • cf) 즉 ratio가 1이 넘으면 잘못된 로직이다.  
    • 회원 수를 카운트 하기 위해 반드시 "DISTINCT"를 넣는다.
    • 넣지 않으면 같은 USER_ID를 카운트해서 잘못된 값이 나온다. 
  • 1) TB_회원가입: 회원 가입연월 테이블: YEAR MONTH USER_ID
  • 2) TB_회원수:  2021년 가입한 회원 수 구하기, (이 테이블은 그냥 회원 수만 구하는 용도라서 JOIN할 때 따로 조건이 필요없음)
  • 3) TB_회원가입, TB_회원수, ONLINE_SALE 세 테이블을 조인한다.

 


특정 기간동안 대여 가능한 자동차들의 대여 비용 구하기 - level 4

출처 - https://school.programmers.co.kr/learn/courses/30/lessons/157339

 

  Sol)

<hide/>
SELECT C.CAR_ID, C.CAR_TYPE, ROUND(DAILY_FEE * 30 * (1 - (DISCOUNT_RATE /100)) , 0) FEE
FROM CAR_RENTAL_COMPANY_CAR C
    JOIN CAR_RENTAL_COMPANY_RENTAL_HISTORY USING (CAR_ID)
    JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN P USING (CAR_TYPE)
WHERE 
    P.DURATION_TYPE = "30일 이상"
    AND CAR_TYPE IN("세단", "SUV")
    AND C.CAR_ID NOT IN  (
        SELECT CAR_ID
        FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
        WHERE
            YEAR(START_DATE) = 2022 AND MONTH(START_DATE) = 11
        OR  YEAR(end_date) = 2022 AND MONTH(end_date) = 11
        OR  START_DATE < "2022-11-01" AND "2022-12-01" <= END_DATE )
GROUP BY 1 HAVING FEE >= 500000 AND FEE < 2000000
ORDER BY 3 DESC, 2, 1 DESC
  • 포인트 1) 할인이 적용된 30일 간의 대여 비용만 구하면 된다. 
    • DATEDIFF를 구할 필요가 없다. 
  • 포인트 2) 각 차량에 기록에 대한 대여 비용의 합을 구하는게 아니라 단순히 30일 * 하루 비용 * (1 - 할인 비율) 만을 구하고 대여 비용이 50만원 이상 200만 원 이하인 경우를 구하면된다. 
  •  포인트 3) 11월에 대여 가능한 조건을 구하기 위해  WHERE 절에 
    • WHERE 절에 END_DATE   < "2022-11-01" OR "2022-12-01" <= START_DATE
    • 라는 조건을 준다면  잘못된 쿼리가 나온다. 
    • 이  WHERE 절은 시작일, 마지막날이 모두 11월에 포함되는 경우만을 포함한다. 따라서, "시작일과 마지막 날 중에 어느 한 쪽만 11월인 경우"가 누락되어있어서 오답이다. 
  • WHERE절 안에 조건으로
    • 1. 시작일이 11월이거나
    • 2. 마지막날이 11월이거나
    • 3. 시작일과 마지막날이 11월 전체를 포함하거나 
    • 이렇게 세 가지 경우를 전체로 묶어서 반대의 경우를 WHERE 절 조건으로 줬다. 
    • 그런데 이렇게 하는 것보다 아래의 서브쿼리가 훨씬 짧고 간단하다. 
    • 다른 블로그에서 참고하고 조건을 이해하기 힘들었지만 수직선 상에 그려보니까 이해갔다. 
    • 11월의 하루라도 포함하도록 조건을 주려면 다음과 같이 WHERE 절 조건을 줄 수 있다. 
      • 이는 START_DATE가 항상 END_DATE보다 이전이라는 성질을 이용했기 때문에 식이 간단해진 것이다. 

 

(간단한 버전 서브쿼리) - 출처 https://velog.io/@greaceh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-SQL-%ED%8A%B9%EC%A0%95-%EA%B8%B0%EA%B0%84%EB%8F%99%EC%95%88-%EB%8C%80%EC%97%AC-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%9E%90%EB%8F%99%EC%B0%A8%EB%93%A4%EC%9D%98-%EB%8C%80%EC%97%AC%EB%B9%84%EC%9A%A9-%EA%B5%AC%ED%95%98%EA%B8%B0

SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE START_DATE <= "2022-11-30" AND END_DATE >= "2022-11-01"

 

서브쿼리 안의 조건을 수직선 상에 나타냈다.&nbsp; START_DT 는 항상 END_DATE보다 작거나 같기 때문에 해당 조건은&nbsp; 11월의 어느 하루라도 포함하는 조건이라고 볼 수 있다.

 


 

 

 

회고록

  • 드디어 프로그래머스의 SQL문제를 다 풀었다 🙌🙌
  • 아무래도 JOIN, GROUP BY 파트가 가장 어려웠던 것 같다. 
  • 될  것 같은데... 내 머릿속에서는 맞는 로직인데 왜 안될까? 하는 생각을 100번도 넘게 했다 ㅎㅎ
  • 그래도 몇 시간씩 매달리다보면 내 답안에서 하나 둘 허점을 찾아서 해결할 수 있었다. 
  • 가장 기억에 남는 건 "SET" 을 이용해서 새로운 변수를 생성해야했던 문제(행 번호 생성 문제 - 입양 시각 구하기 (2))  이다. 내 배경지식으로는 도저히 풀 수 없는 문제였다. 이렇게 배경 지식 없이는 못 푸는 경우도 간혹 있기 때문에 정해진 시간을 넘는 경우는 좋은 답안을 보고 이해하는 게 훨씬 효율적인 방법이라고 몸소 느꼈다. 
  • 앞으로는 Leet code 나 다른 사이트에서 문제를 풀면서 감을 유지하려고 한다!