현업에서 서브쿼리보다 WITH구문을 더 선호한다는 이야기를 들었지만... 상황에 따라 적절한 구문을 잘 활용할 수 있는 게 최선일 테다. 처음으로 HARD가 있는 챕터. 화이팅!
[EASY] 1978. Employees Whose Manager Left the Company
LEFT JOIN / IS NULL
⏰ 8:30
LEFT JOIN RIGHT JOIN을 각각 어떤 상황에서 써야 하는지, 결과값이 어떻게 나오는지 아직도 약-간 헷갈린다.
반복 연습이 답!
SELECT E1.EMPLOYEE_ID
FROM EMPLOYEES E1
LEFT JOIN EMPLOYEES E2
ON E1.MANAGER_ID = E2.EMPLOYEE_ID
WHERE E1.SALARY < 30000
AND E2.EMPLOYEE_ID IS NULL
AND E1.MANAGER_ID IS NOT NULL
ORDER BY 1
[MEDIUM] 626. Exchange Seats
MOD / CASE
⏰ 5:31
[배움 1] MOD 함수 = % 연산자
[정답 1] SELF JOIN으로 STUDENT 행 위아래로 밀고 붙여 출력하기
SELECT S1.ID,
CASE WHEN MOD(S1.ID,2) = 1 AND S2.ID IS NULL THEN S1.STUDENT
WHEN MOD(S1.ID,2) = 1 THEN S2.STUDENT
WHEN MOD(S1.ID,2) = 0 THEN S3.STUDENT
END AS STUDENT
FROM SEAT S1
LEFT JOIN SEAT S2
ON S1.ID + 1 = S2.ID
LEFT JOIN SEAT S3
ON S1.ID - 1 = S3.ID
ORDER BY 1
[정답 2] ID 열을 무식하게(?) 더하고 빼서 STUDENT 열과 매칭한 뒤 정렬하기
SELECT CASE WHEN ID % 2 = 1 AND ID = (SELECT MAX(ID) FROM SEAT) THEN ID
WHEN ID % 2 = 1 THEN ID + 1
ELSE ID - 1
END AS ID,
STUDENT
FROM SEAT
ORDER BY 1
[MEDIUM] 1341. Movie Rating
UNION / 서브쿼리
⏰ 18:20
이젠... 답을 맞추는 것보다 남들이 얼마나 더 쉽게 풀었을까 얼른 보고싶어...
는 모두 이렇게 풀었다! 다른 값을 뽑은 다음 하나의 열, 하나의 테이블로 합칠 때는 UNION 이 최선인가보다.
[배움 1] UNION을 할 때 LIMIT를 함께 쓰고 싶으면 괄호로 묶어야 한다
(SELECT U.NAME AS RESULTS
FROM MOVIERATING R
JOIN USERS U
USING (USER_ID)
GROUP BY R.USER_ID
ORDER BY COUNT(*) DESC, U.NAME
LIMIT 1)
UNION ALL
(SELECT M.TITLE AS RESULTS
FROM MOVIERATING R
JOIN MOVIES M
USING (MOVIE_ID)
WHERE DATE_FORMAT(CREATED_AT, '%Y-%m') = '2020-02'
GROUP BY R.MOVIE_ID
ORDER BY AVG(RATING) DESC, M.TITLE
LIMIT 1)
[MEDIUM] 1321. Restaurant Growth
WINDOW
⏰ 29:01
[정답 1] 서브쿼리 안에서 윈도우 함수 활용하기
SELECT
DISTINCT VISITED_ON,
AMOUNT,
ROUND(AMOUNT / 7, 2) AS AVERAGE_AMOUNT
FROM (SELECT VISITED_ON,
SUM(AMOUNT) OVER (
ORDER BY VISITED_ON
RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW
) AS AMOUNT,
MIN(VISITED_ON) OVER() 1ST_DATE
FROM CUSTOMER) A
WHERE VISITED_ON >= 1ST_DATE + INTERVAL 6 DAY
[정답 2] 본쿼리에서 윈도우함수 활용하고 LIMIT와 OFFSET으로 시작 범위 정하기
SELECT
DISTINCT VISITED_ON,
SUM(AMOUNT) OVER (
ORDER BY VISITED_ON
RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW) AS AMOUNT,
ROUND(AVG(AMOUNT) OVER (
ORDER BY VISITED_ON
RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW),2) AS AVERAGE_AMOUNT
FROM (SELECT VISITED_ON, SUM(AMOUNT) AS AMOUNT
FROM CUSTOMER
GROUP BY VISITED_ON) C
LIMIT 6,1000
[배움 1] 윈도우 함수 범위 지정 시, ROWS BETWEEN 과 RANGE BETWEEN의 차이 / PRECEDING 과 FOLLOWING
[Medium] 602. Friend Requests II: Who Has the Most Friends
UNION ALL / WITH
⏰ 28:08
처음에 WITH구문으로 일단 DISTINCT ID의 리스트를 뽑아놓고... 옆에 각 열의 친구 수를 뽑은 다음 더해서 구하려고 했는데 그렇게 복잡하게 풀 필요가 하나도 없었다... 친구가 누구인지는 안 중요하고 그냥 COUNT만 하면 되니까 무식하게 각 ID가 몇번 쓰였는지만 합쳐서 세면 되는거였다. WOW...
문제를 보고 이렇게 쉽게 풀 수 있는 방법을 어떻게 떠올리지? 더 많이 풀어보면 되는걸까? 혹시 천재들은 딱 보면 떠오르는데 내가 못하고 있는 건 아닐까? 재능이 없는걸까?
(이상 진도를 나가면 나갈수록 오히려 자신감을 잃어가는 초보 SQL러의 합리적 의심이었습니다...
먼 훗날 돌아와서 이런 생각 했던 꼬꼬마 시절의 나를 귀여워하며 웃을 수 있는 날이 오길...)
[정답 1] WITH구문과 LIMIT 활용해 풀기
WITH ALL_ID AS (
SELECT REQUESTER_ID AS ID
FROM RequestAccepted
UNION ALL
SELECT ACCEPTER_ID AS ID
FROM RequestAccepted
)
SELECT ID, COUNT(*) AS NUM
FROM ALL_ID
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1
[정답 2] WITH구문과 RANK 윈도우함수 활용해 풀기
WITH ALL_ID AS (
SELECT REQUESTER_ID AS ID
FROM RequestAccepted
UNION ALL
SELECT ACCEPTER_ID AS ID
FROM RequestAccepted
)
SELECT ID, NUM
FROM (SELECT ID, COUNT(*) AS NUM,
RANK() OVER (ORDER BY COUNT(*) DESC) AS RNK
FROM ALL_ID
GROUP BY ID) A
WHERE RNK = 1
[Medium] 585. Investments in 2016
WITH / IN
⏰ 18:08
세상에... 그렇게 바로 다음 문제에서 나는 천재가 되었다! (감격)
...그리고 물론 더 쉽게 푸는 법이 있었다 ^~^
[정답 1] WITH구문으로 조건 2개 각각 임시 테이블화하기
WITH LIST_2015 AS (
SELECT PID, COUNT(*) OVER (PARTITION BY TIV_2015) 2015_CNT
FROM INSURANCE
),
LIST_CITY AS (
SELECT PID, COUNT(*) OVER (PARTITION BY LAT, LON) CITY_CNT
FROM INSURANCE
)
SELECT ROUND(SUM(TIV_2016),2) tiv_2016
FROM INSURANCE
WHERE PID IN (SELECT PID
FROM LIST_2015 A JOIN LIST_CITY B USING (PID)
WHERE CITY_CNT = 1 AND 2015_CNT > 1
)
[정답 2] WHERE 절에서 GROUP BY와 HAVING으로 조건 2개 바로 걸러내기
GROUP BY만 걸고 HAVING에서 저런식으로 대뜸 COUNT를 쓸 수 있는지 몰랐다...
뭘 썼을 때 어떤 건 SELECT에서 명시해줘야 하고, 어떤 건 FROM에서 한 번 더 걸러줘야 하고, 어떤 건 WHERE에서 바로 검색이 가능한지, 내가 명명한 열 이름이 어떤 순서로 만들어져 어디서 검색할 때 써먹을 수 있는건지 등등... 기본적인 작동조건 자체에서 아직 헷갈리는 부분이 많다. 연습만이 살길일세...
SELECT ROUND(SUM(TIV_2016),2) tiv_2016
FROM INSURANCE
WHERE TIV_2015 IN (SELECT TIV_2015
FROM INSURANCE
GROUP BY TIV_2015
HAVING COUNT(*) > 1
) AND
(LAT,LON) IN (SELECT LAT, LON
FROM INSURANCE
GROUP BY LAT, LON
HAVING COUNT(*) = 1
)
[Hard] 185. Department Top Three Salaries
WITH / DENSE_RANK
⏰ 6:26
나 김난리... 천재로 거듭나다. (??)
리트코드의 50문제 중 유일한 HARD 난이도라 무지 X100 쫄았는데 5분만에 뚝딱 풀었다. 이러면 괜히 기분 좋으니까 난이도 시스템 다시 손보시라고요~!!
WITH LIST AS (SELECT D.NAME DEPARTMENT,
E.NAME EMPLOYEE,
SALARY,
DENSE_RANK() OVER (PARTITION BY D.NAME ORDER BY SALARY DESC) RNK
FROM EMPLOYEE E
JOIN DEPARTMENT D
ON E.DEPARTMENTID = D.ID
)
SELECT Department, Employee, Salary
FROM LIST
WHERE RNK <= 3
ORDER BY 1
헉헉. 오늘 문제 너무 어려워서 3문제 풀고 공부하는데 2시간 걸림. 오늘은 여기까지만 해야겠다.
-
리트코드 SQL 50문제 전부 풀이 완료~! 3주간 하루도 안 빼놓고 매일 풀었다는 게 스스로도 잘 믿기지 않는다. 왕뿌듯!
이 기세로 프로그래머스 남은 문제도 설 전까지 몽땅 부셔보자!
'[공부] SQL' 카테고리의 다른 글
[SQL 코딩테스트] 해커랭크 Advanced Select 정답 (0) | 2024.02.16 |
---|---|
[SQL 코딩테스트] 해커랭크 Basic Select 정답 (1) | 2024.02.15 |
[SQL 코딩테스트] 리트코드 Advanced Select and Joins 정답 (0) | 2024.01.31 |
[SQL 코딩테스트] 리트코드 Advanced String Functions / Regex / Clause 정답 (0) | 2024.01.29 |
[SQL 코딩테스트] 프로그래머스 JOIN 정답 (0) | 2024.01.26 |