링크: https://leetcode.com/problems/human-traffic-of-stadium/

 

습득한 점: 

연속되는 숫자를 찾는 새로운 아이디어.

GROUP BY, PARTITION BY 차이 재확인.

hard 난이도는 아니었다.

 


문제:

사람 수 (people)가 100이상일 때, 3개 이상의 연속되는 "id"값에 대한 행을 모두 조회하는 쿼리 작성 

결과값은 "visit_date" 기준으로 오름차순 정렬

 

 

문제파악:

일단 visit_date는 정렬 빼고는 신경 쓸 필요는 없을 것 같았고,

우선 사람 수가 100명 이상인 테이블을 걸러낸 결과값을

WITH문으로 테이블을 새롭게 구성한 뒤 쿼리 작성을 시작하면 될 것 같다.

 

대략적으로 2가지 풀이법이 보인다. 셀프 조인 혹은 윈도우 함수 사용하기

 

풀이:

 

1. 'people'이 100 이상인 행들을 걸러냈을 때, id 값의 모양을 예상해본다.

데이터 사이사이에 100보다 낮은 값들이 포함되어 있었을 것이니,
아래 표와 같이 row_number()를 활용해서 강제적으로 맨 위부터 순위를 매긴 후
id 값에서 row_number값을 뺀 차이값 diff를 구하면,
연속되는 id값에서 벗어나는 순간에 diff값은 숫자가 바뀌게 된다.

 

ex) 아래 표를 예시로 든다면, id_1이 4에서 6으로 변할때, 차이값은 0에서 1로 변한다.

ex), id_2에서 5로 변할때, 차이값은 0에서 2로 변한다.

id_1 row_number diff_1 id_2 row_number diff_2
1 1 0 1 1 0
2 2 0 2 2 0
3 3 0 5 3 2
4 4 0 6 4 2
6 5 1 7 5 2
7 6 1 9 6 3

 

일종의 파티션을 나눈다고 생각하면 된다.

이런식의 계산을 이용하려면, 반드시 id 값을 기준으로 오름차순 정렬이 먼저 이루어져 있어야 한다.

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)​

 

 

2.  diff를 기준으로 GROUP BY 이후,  행의 갯수 조회

 

기존 만들어진 WITH문을 그대로 이용해서 SELECT문에는 count(*)을 추가하고,  diff 기준으로 groupby,
HAVING count(*) >= 3을 조건으로 쿼리를 실행하면
diff의 그룹별 행의 갯수가 표현된다.

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)

, Ranked AS (
    SELECT id
         , visit_date
         , people
         , diff
         , COUNT(*) AS consecutive_num
    FROM Sorted
    GROUP BY diff
    HAVING COUNT(*) >=3
)

3. 찾아낸 diff를 활용하여 정답 찾기

"consecutive_num" 은 3이상이면 나타나는 값이니 신경 쓸 필요는 없다.

2번의 테스트 케이스에 따르면, diff 가 2인 것이 연속된 id 4개를 가지고 있다는 것이니, 
연속되는 id값의 행들만 찾고 싶다면, diff를 기준으로 Sorted 테이블과 inner 조인 시켜주면 된다.

문제에 날짜기준 오름차순 정렬조건도 있지만, id가 커짐에따라 방문 일자도 하루씩 늘어나기 때문. 

 

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)

, Ranked AS (
    SELECT id
         , visit_date
         , people
         , diff
         , COUNT(*) AS consecutive_num
    FROM Sorted
    GROUP BY diff
    HAVING COUNT(*) >=3
)

SELECT Sorted.id
     , Sorted.visit_date
     , Sorted.people
       
FROM Sorted
   INNER JOIN Ranked ON Sorted.diff = Ranked.diff

 

 

 

PARTITION BY 이용:

WITH Sorted AS (
    SELECT id
         , visit_date
         , people
         , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
    FROM Stadium
    WHERE people >= 100
),
    consecutive_count AS (
        SELECT id
            , visit_date
            , people
            , diff
            , COUNT(*) OVER (PARTITION BY diff) AS consecutive_num
        FROM Sorted
    )

SELECT *
FROM consecutive_count
ORDER BY id

 

큰 차이는 없지만, PARTITION BY를 활용하면 더욱 직관적으로 쿼리를 작성할 수 있다.

무엇보다 GROUP BY는 조건에 따른 그룹의 첫번째 행만을 남기고, 이외 행은 다 날리게 되지만
(아래 output케이스는 Ranked 테이블에서 HAVING count(*) >=3 조건만 없다면 count(*) id가 2, 5인 행만 남을 것이다)

PARTITION BY는 조건에 해당하는 모든 id 행을 보여준다.

이후 consecutive_num >=3  인 값들만 찾으면 된다.

또한 Sorted테이블 이후에는, 정렬순서가 유지되지 않을 수도 있기에

+ ORDER BY id 추가해주면 된다.

 

 (*** GROUP BY를 활용하는 경우, 이미 id 기준 오름차순 정렬된 테이블에 inner 조인하는 것이 때문에 따로 추가할 필요는 없다)

 

https://school.programmers.co.kr/learn/courses/30/lessons/157340

 

습득한 점:

레벨 3문제지만, 코드 길이에 관계없이 지금까지 풀었던 문제중에서 가장 까다로웠고,

깨닫게 된 점이 많았다.

 

문제를 마주하면 이해하기 좋은 코드 작성하고자,

반복적으로 필요하지 않고 해당 함수가 꼭 필요하지 않음에도, With 구문을 습관적으로 활용해서 문제를 풀곤 했었다.

 

덕분에 이해하기는 편했으나, 코드 줄 수가 너무 길어지는 단점이 있었다.

결과적으론... 이해하는 것이 코드를 작성한 나만 쉽지 않을까? 생각들었다.

DA가 sql 코드 성능을 신경 쓸 필요는 별로 없겠지만, 그래도 기존에 해왔던 파이썬이나 자바처럼 좀 더 컴팩트하게 작성하면 좋지 않을까?

 

앞으로는 가독성도 가독성이지만, 최대한 컴팩트하게 작성해보고자 한다.


- IF구문의 새로운 활용법:

* 기본적으로 IF(조건,True값,False값) 의 형태로 작성되지만,
  
IF(컬럼 IN (원하는 서브쿼리),True,False) 의 형태로도 작성될 수 있다.

 

- GROUP BY 특성:

* GROUP BY를 MAX()와 같은 함수와 사용할 때, run을 눌러서 나타나는 테이블은 컬럼들이 MAX()함수로 계산된 컬럼을 제외하고
   다른 컬럼들은 서로 관계성을 지니지 않음.

* 보통 원본 테이블에서 보여지는 그룹 별 첫번째 값들이 보여지게 되어 있음.

 

 

 

문제:

CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서

 

2022년 10월 16일에 대여 중인 자동차인 경우 '대여중' 이라고 표시하고,

대여 중이지 않은 자동차인 경우 '대여 가능'을 표시하는 컬럼(컬럼명: AVAILABILITY)을 추가하여

자동차 ID와 AVAILABILITY 리스트를 출력하는 쿼리 작성

 

이때 반납 날짜가 2022년 10월 16일인 경우에도 '대여중'으로 표시

결과는 자동차 ID를 기준으로 내림차순 정렬

 

 

 

문제파악:

2022-10-16일에 대여가 가능한지의 여부를 찾는 문제

CAR_ID를 고유값으로 뽑아내야 해서 많이 헤맷다.

위 예시에서, CAR_ID의 2,3번째 데이터는 대여시작일과 대여종료일 사이에 10-16가 걸쳐있어서

결과테이블에서는 '대여중'으로 표시되어야 하고 나머지 데이터는 표현되지 않아야 한다.

 

IF구문 내, 서브쿼리를 활용해서,

조건에 해당되는 값이, 서브쿼리로 인해 뽑힌 데이터 내에 단 한개라도 들어있다면, '대여중', 

존재하지 않는다면, '대여 가능'으로 표시되도록 작성 후 alias 를 지정한 뒤, 이를 SELECT절에 CAR_ID 컬럼과 함께 삽입했다.

 

이로써 조건에 맞는 CAR_ID와, 대여 여부를 판단한 AVAILABILITY를 테이블에서 확인할 수 있다.

 

 

 

 

풀이:

SELECT DISTINCT CAR_ID
              , IF(CAR_ID IN(SELECT CAR_ID
                             FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
                             WHERE '2022-10-16' BETWEEN START_DATE AND END_DATE),'대여중','대여 가능') AS AVAILABILTIY

FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
ORDER BY CAR_ID DESC

 

인터넷 상에  죄다 LIMIT 1 풀이 투성이라, 조금 헤멧다.

리팩토링은 나중에...해봐야겠다.

 

링크: https://school.programmers.co.kr/learn/courses/30/lessons/131124

 

습득한 점:

WITH 구문을 사용 함과 동시에, 다른 2가지 이상의 테이블을 동시에 활용 할때,

FROM 절에 들어할 테이블이 2개 이상인 경우가 있어서

이를 어찌 처리해야 할지 난감했다.

 

결과적으로는 WITH 문 + SELECT 문 조합으로 계산을 끝낸 뒤,
이 전체를 새로운 테이블의 WHERE 값에 넣어버리는 식으로 쿼리를 작성했다.

 

이 과정에서, 아래 2가지의 차이를 알게되었다.

WHERE 컬럼 = (~~~~)

WHERE 컬럼 IN (~~~~)

 

서브쿼리에 해당하는 값이 1가지 이고, 이를 다른 테이블의 조건값으로서 매칭 시킬 때는 "=" 을 사용

서브쿼리에 해당하는 값이 2가지 이상이고, 이 때는 "IN" 을 활용해야

 

subquery returns more than 1 row 에러가 발생하지 않는다.

 

또한 내가 생각치 못한 경우의 수가 있는지 없는지, 테이블을 뜯어보는 습관은 좋은 것 같다.

(이러면 시간이 오래걸려서 실제 코테에선 맞는 방법일 지는 모르겠다)


 

문제:

MEMBER_PROFILE와 REST_REVIEW 테이블에서 리뷰를 가장 많이 작성한 회원의 리뷰들을 조회하는 쿼리 작성

결과값은 회원 이름, 리뷰 텍스트, 리뷰 작성일이 출력되도록 작성,

정렬기준은 리뷰 작성일을 기준으로 오름차순,

                 리뷰 작성일이 같다면 리뷰 텍스트를 기준으로 오름차순 정렬.

 

 

문제파악:

문제가 좀 이상하다.

문제 타이틀은 식당 목록을 출력하는 것인데,

정답의 결과는 아래와 같다.

 

또 하나, 문제를 처음 접근할 때 조인을 어떻게하면 좋을 지 처음엔 테이블을 하나하나 다 뜯어보다가,

고객을 정보를 담은 MEMBER_PROFILE 테이블의 member_id(이메일)는 14개인데,

리뷰정보를 담은 REST_REVIEW 테이블에서의 중복을 제거한 member_id(이메일)은 모두 15개 였다...

???

일단 테이블끼리 조인했다.

 

또한 리뷰 카운트를 세다보니, 리뷰 수의 최댓 값이 같은 사람이 한명이 아니었다.

리뷰를 가장 많이 작성한 사람이 3명 임에도,

아래의 쿼리와 같이 LIMIT 1을 조건으로 걸면, 저 위에 리뷰 3개를 작성한 사람 3명 중 1명만 조회가 된다.

 

LIMIT 1 을 조건으로 하고, 쿼리를 작성한 사람이 꽤 되었는데.. 이렇게 작성해도 정답처리가 되는 듯 했다.

여러모로 수정이 필요한 문제인듯하다. 시스템 자체 테스트 케이스의 갯수가 좀 적은 듯 하다.

 

SELECT A.MEMBER_NAME,B.REVIEW_TEXT,DATE_FORMAT(B.REVIEW_DATE, "%Y-%m-%d") AS REVIEW_DATE
from MEMBER_PROFILE A join REST_REVIEW B
on A.MEMBER_ID = B.MEMBER_ID
WHERE A.MEMBER_ID = (SELECT MEMBER_ID FROM REST_REVIEW
GROUP BY MEMBER_ID
ORDER BY COUNT(*) DESC LIMIT 1)
order by REVIEW_DATE asc, REVIEW_TEXT

 

 

풀이:

 

1. 리뷰 수 최대 값 찾기

WITH 문으로, MEMBER_ID를 그룹으로 묶은 뒤 MEMBER_ID별 카운트 값을 찾기

그 카운트 값들 중 최고인 값을 SELECT MAX(Review_NUMBER)로 찾고 Alias는 MAX_NUMBER로 지정

 

2. 리뷰 수가 최대값 인 아이디 찾기

REST_REVIEW 테이블에서 SELECT 문으로 MEMBER_ID, MEMBER_ID별 카운트 값을 찾는 쿼리 작성 후 이를 서브쿼리로 활용

만들어진 서브쿼리를 테이블로서 활용하고, MEMBER_ID값을 다시 구함.

이떄 WHERE 구문 조건으로 WHERE NUM = (1번 쿼리 전체) 을 조건으로 추가.

그럼 리뷰 카운트가 3인, MEMBER_ID값 만 뽑아낼 수 있다.

 

3. 찾은 아이디를 활용하여 정답 찾기

뽑아낸 MEMBER_ID 들을 WHERE 조건으로 활용하는 쿼리문을 작성

테이블 2가지를 조인 후, 정답에서 원하는 컬럼들을 다 가지고 온다.

이때는 WHERE 조건에 들어가는 값들이 1개 이상이므로 IN을 활용하여,
WHERE RR.MOMBER_ID IN (2번 쿼리 전체) 를 조건으로 활용.

뒤에 ORDER BY 정렬조건까지 잊지말고 작성.

 

 

SELECT MEMBER_NAME
     , REVIEW_TEXT
     , DATE_FORMAT(REVIEW_DATE, "%Y-%m-%d") AS REVIEW_DATE
     
FROM REST_REVIEW AS RR
     LEFT JOIN MEMBER_PROFILE AS MP ON RR.MEMBER_ID = MP.MEMBER_ID
WHERE RR.MEMBER_ID IN (

                    SELECT MEMBER_ID
                    FROM(
                        SELECT MEMBER_ID, count(*) AS NUM
                        FROM REST_REVIEW
                        GROUP BY MEMBER_ID
                        ) sub
                    WHERE NUM = (

                        WITH counter as (
                            SELECT MEMBER_ID, COUNT(*) AS Review_NUMBER
                            FROM REST_REVIEW
                            GROUP BY MEMBER_ID
                                         )

                        SELECT MAX(Review_NUMBER) AS MAX_NUMBER
                        FROM counter
                                )
                        
                       )
ORDER BY REVIEW_DATE
       , REVIEW_TEXT

 

+ Recent posts