JOIN에 대해 알아보자
RDB의 특징인 관계를 맺어주어 다른 테이블의 값을 가져올수있는 JOIN
JOIN의 종류
출처: https://hongong.hanbit.co.kr/sql-기본-문법-joininner-outer-cross-self-join/
위 출처를 대강 훑고 오면, 기초적인 JOIN은 INNER JOIN임을 알게 된다.
INNER JOIN
- 기초적인 JOIN의 디폴트 방식이다.
- A, B 테이블의 교집합을 구한다.
LEFT OUTER JOIN
- 차집합을 구할때 쓰인다.
- LEFT OUTER JOIN
- LEFT OUTER JOIN을 활용해 나는 가지고 있지만 다른 테이블엔 없는 애를 찾고 싶다. 아래가 그 코드
SELECT * FROM A a LEFT JOIN B b ON a.KEY = b.KEY WHERE b.KEY IS NULL
RIGHT OUTER JOIN
- 반대방향의 차집합을 구할 수 있다.
- RIGHT OUTER JOIN
SELECT * FROM A a RIGHT OUTER JOIN B b WHERE a.KEY IS NULL
JOIN 활용
오랜 기간 보호한 동물 (1)
링크: https://school.programmers.co.kr/learn/courses/30/lessons/59044
-- 코드를 입력하세요
SELECT I.name,
I.datetime
FROM animal_ins AS I
LEFT JOIN animal_outs AS O
ON I.animal_id = O.animal_id
WHERE O.animal_id IS NULL
ORDER BY I.datetime
LIMIT 3;
이 문제는 위에서 훑어본 LEFT JOIN 을 활용하여 차집합을 구하는 문제였다. LEFT JOIN의 개념을 알면 간단한 문제였다.
있었는데요 없었습니다
링크: https://school.programmers.co.kr/learn/courses/30/lessons/59043
-- 코드를 입력하세요
SELECT I.animal_id,
I.NAME
FROM animal_ins AS I
JOIN animal_outs AS O
ON I.animal_id = O.animal_id
WHERE I.datetime > O.datetime
ORDER BY I.datetime;
오랜 기간 보호한 동물 (2)
링크: https://school.programmers.co.kr/learn/courses/30/lessons/59411
-- 코드를 입력하세요
SELECT O.animal_id,
O.name
FROM animal_ins AS I
RIGHT OUTER JOIN animal_outs AS O
ON I.animal_id = O.animal_id
WHERE O.animal_id IS NOT NULL
ORDER BY Datediff(O.datetime, I.datetime) DESC
LIMIT 2;
이 문제는 위에서 훑어본 RIGHT OUTER JOIN 을 활용해 차집합을 구하는 문제였다. 사실 반대로 FROM 을 걸고, LEFT JOIN을 걸어도 동일한 결과가 나왔을것 같은데… 우선 나는 RIGHT OUTER JOIN을 활용해 풀었다.
둘의 차이가 궁금해서 구글링을 좀 해봤는데, 차이가 없고 위차만 다르다는 말이 있네..
뭔가 내가 모르는 차이점이 있을것 같다. 예를 들면 왼쪽 오른쪽의 기준을 어떤걸로 하느냐에 따라 같은 결과지만 다른 성능을 보인다던지? 이건 SQL 문 다 풀고나서 책을 통해 배워야겠다.
이외 Level3 문제
조건별로 분류하여 주문상태 출력하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/131113
-- 코드를 입력하세요
SELECT ORDER_ID,
PRODUCT_ID,
DATE_FORMAT(OUT_DATE, "%Y-%m-%d") AS OUT_DATE,
(CASE
WHEN OUT_DATE IS NULL THEN "출고미정"
WHEN OUT_DATE <= "2022-05-01" THEN "출고완료"
ELSE "출고대기"
END) AS 출고여부
FROM FOOD_ORDER
ORDER BY ORDER_ID;
카테고리 별 도서 판매량 집계하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/144855
-- 코드를 입력하세요
SELECT category,
Sum(S.sales) AS TOTAL_SALES
FROM book AS B
JOIN book_sales AS S
ON B.book_id = S.book_id
WHERE Year(S.sales_date) = 2022
AND Month(S.sales_date) = 1
GROUP BY category
ORDER BY category
없어진 기록 찾기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/59042
-- 코드를 입력하세요
SELECT o.animal_id,
o.NAME
FROM animal_ins AS i
RIGHT OUTER JOIN animal_outs AS o
ON i.animal_id = o.animal_id
WHERE i.animal_id IS NULL
헤비 유저가 소유한 장소
링크: https://school.programmers.co.kr/learn/courses/30/lessons/77487
-- 코드를 입력하세요
SELECT id,
NAME,
host_id
FROM places
WHERE host_id IN (SELECT host_id
FROM places
GROUP BY host_id
HAVING Count(host_id) > 1)
ORDER BY id
와 이게 풀렸네??
엄청 난해하다고 생각했다가 혹시 { WHERE SELECT문 안에서 host_id가 있으면? } 으로 찾으면 가능할지 해봤는데 되더라.
이게 말로만 듣던 서브쿼리라고한다. 쿼리안의 쿼리… 신기했다.
그리고 개선점을 찾기 위해 구글링해보니 첫 포스팅에 이런 내용이 있었다.
출처: https://velog.io/@rgunny/SQLProgrammers-헤비-유저가-소유한-장소
Exists를 통해 true false 만 확인하고 얼리 불필요한 연산이 줄어든다는 설명인데 일단 그렇구나 하고 넘어갈 생각이다. 자세한 최적화는 책을 통해 배울거라서.. ㅎㅎ
-- 코드를 입력하세요
SELECT *
FROM places P1
WHERE EXISTS (SELECT 1
FROM places P2
WHERE P1.host_id = P2.host_id
GROUP BY host_id
HAVING Count(host_id) > 1)
ORDER BY id
이런게 있구나, In 보다 Exists가 빠를 가능성이 있구나 정도로 넘어갈 생각이다.
대여 기록이 존재하는 자동차 리스트 구하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/157341#
-- 코드를 입력하세요
SELECT DISTINCT CAR.car_id
FROM car_rental_company_car AS CAR
JOIN car_rental_company_rental_history AS HISTORY
ON CAR.car_id = HISTORY.car_id
WHERE CAR.car_type = "세단"
AND Month(HISTORY.start_date) = 10
ORDER BY CAR.car_id DESC;
즐겨찾기가 가장 많은 식당 정보 출력하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/131123
-- 코드를 입력하세요
SELECT food_type,
rest_id,
rest_name,
favorites
FROM rest_info
WHERE ( food_type, favorites ) IN (SELECT food_type,
Max(favorites)
FROM rest_info
GROUP BY food_type)
ORDER BY food_type DESC;
GROUP BY HAVING으로 풀랬는데 잘 안되어서 검색해보았더니 WHERE에 IN을 사용해서 했더라. 잘 이해안되는데 억지로 짜맞춰보면 WHERE절에 비교 대상인 애들이 ( food_type, favorites )라는 집합이고, 그 집합이 max로 맞춘 1열짜리 데이터에 있는지 확인하는 방식인것 같다.
조건에 맞는 사용자와 총 거래금액 조회하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/164668
-- 코드를 입력하세요
SELECT user_id,
nickname,
Sum(price) AS TOTAL_SALES
FROM used_goods_user AS USER
JOIN used_goods_board AS board
ON board.writer_id = USER.user_id
WHERE status = "done"
GROUP BY user_id
HAVING total_sales >= 700000
ORDER BY total_sales;
분명 그룹 바이 하면서 많이 접했던 유형인데, Sum을 구하는데 꽤 헤매었다.
결국 처음부터 다시 짜서 해결했다. 이게 작은 조각들을 순차적으로 맞추는 느낌으로 가야 되는게 알고리즘 문제풀이랑 결국 방법은 똑같다는게 느껴진 문제다.
조건에 맞는 사용자 정보 조회하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/164670
-- 코드를 입력하세요
SELECT user_id,
nickname,
Concat(USER.city, " ", USER.street_address1, " ", USER.street_address2)
AS
전체주소,
Concat(LEFT(tlno, 3), "-", Mid(tlno, 4, 4), "-", RIGHT(tlno, 4))
AS 전화번호
FROM used_goods_user AS USER
JOIN used_goods_board AS board
ON USER.user_id = board.writer_id
GROUP BY user_id
HAVING Count(user_id) >= 3
ORDER BY user_id DESC;
답을 내긴 했는데, Concat 부분이 너무 지저분하다. 분명히 이것보다 더 좋은 방법이 있을것같다.
출처: https://jaimemin.tistory.com/2264
위 링크를 보면 Concat 부분은 동일하게 푼게 보인다. 근데 특이하게 GROUP BY Having Count(user_id) ≥ 3 이 아니라 FROM 에서 가져올 테이블을 서브쿼리로 만든 테이블을 가져왔다.
엄청 신기하다.
머릿속으로 (뇌피셜로) 생각해보면 조인을 먼저 한 뒤에 GROUP BY로 그룹화하는 내 방식이랑 서브쿼리로 그룹화된 테이블을 짠 다음에 조인을 하는게 데이터 핸들링 하는 양이 훨씬 적을 것같다. 아무래도 서브쿼리에서 그룹화를 통해 로우를 엄청 짤라냈기 때문에 훨씬 가벼울것 같다.
아래 코드가 위 링크에 있는 쿼리문
SELECT
USER_ID,
NICKNAME,
CONCAT(CITY, " ", STREET_ADDRESS1, " ", STREET_ADDRESS2) AS 전체주소,
CONCAT(LEFT(TLNO, 3) , "-" , MID(TLNO,4,4) , "-", RIGHT(TLNO,4)) AS 전화번호
FROM
(
SELECT
*
FROM
USED_GOODS_BOARD
GROUP BY
WRITER_ID
HAVING
COUNT(*) >= 3
) AS UGB
INNER JOIN
USED_GOODS_USER AS UGU
ON
UGB.WRITER_ID = UGU.USER_ID
ORDER BY
USER_ID DESC
음… 근데 위 뇌피셜이 사실일수도 있지만 서브쿼리를 돌린다는거 자체가 셀렉 쿼리를 한번 더 친다고 생각할 수도 있는 부분이라 성능비교는 해봐야할것 같다.
출처: https://blog.naver.com/jwyoon25/221289425535
위 글을 보면 “실행계획”을 직접 보면서 어떤 쿼리가 좋은지 확인하는 방식이 눈에 띈다.
또한 결론부분에 “집계 후 조인”을 통해 조인시의 부담을 줄이는게 쿼리 튜닝의 기본이라는 말이 나온다.
어느 정도 유추는 성공했지만 위 내가 비교하고자하는 케이스와는 좀 다른 이야기이긴 하니까 아직 부족하다.
또 같은 이야긴데 일단 기본 쿼리짜는 법을 배운 다음에 책을 통해 쿼리 튜닝을 배워보자.
조회수가 가장 많은 중고거래 게시판의 첨부파일 조회하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/164671
-- 코드를 입력하세요
SELECT Concat("/home/grep/src/", FILE.board_id, "/", FILE.file_id,
FILE.file_name,
FILE.file_ext) AS FILE_PATH
FROM used_goods_file AS FILE
JOIN used_goods_board AS board
ON FILE.board_id = board.board_id
WHERE board.views = (SELECT Max(views)
FROM used_goods_board)
ORDER BY FILE.file_id DESC
조회수 가장 높은 로우를 찾는데 꽤 많은 시간이 걸렸다. level3 까지 왔으나 이미 해본것 중 하나를 몰라서 헤매었네.. 역시 기초가 중요하다
자동차 대여 기록에서 대여중 / 대여 가능 여부 구분하기
링크: https://school.programmers.co.kr/learn/courses/30/lessons/157340
-- 코드를 입력하세요
SELECT car_id,
(CASE
WHEN CAR_ID IN
(
SELECT CAR_ID FROM car_rental_company_rental_history
WHERE "2022-10-16" BETWEEN START_DATE AND END_DATE
)
THEN "대여중"
ELSE "대여 가능"
END) AS AVAILABILITY
FROM car_rental_company_rental_history
GROUP BY car_id
ORDER BY car_id DESC;
진짜 어려웠다.
서브쿼리를 사용하면 되겠다는 건 알았지만 CAR_ID IN () THEN "대여중" 을 사용하면 된다는걸 결국 못알아냈다. IN을 사용도 해봤지만 CAR_ID를 통하지 않고 다른 걸 사용하고 정말 엄청난 삽질을 해댔는데…
우선은 다른 사람 풀이를 보고 해석하고 푼것으로 끝을 낸다. 다음에 꼭 다시 풀어봐야겠다.
'BackEnd > SQL' 카테고리의 다른 글
[Oracle/JPA] Pagination Fetch 구문 에러 (0) | 2023.07.09 |
---|---|
[SQL/ORACLE] ORA-00911: invalid character 에러 (0) | 2023.06.30 |
[SQL/ORACLE] missing left parenthesis 에러 (0) | 2023.06.18 |
SQL공부 feat. 프로그래머스 (6) (Level 4) (0) | 2023.03.21 |
SQL공부 feat. 프로그래머스 (4) (CASE-WHEN-ELSE-END) (0) | 2023.03.19 |
SQL공부 feat. 프로그래머스 (3) (JOIN - ON, GROUP BY - HAVING) (0) | 2023.03.19 |
SQL문 연습 with 프로그래머스 (2) (MySQL) (0) | 2023.02.11 |
SQL문 연습 with 프로그래머스 (1) (MySQL) (0) | 2023.02.08 |
댓글