SQL JOIN 톺아보기
[SQL]****SQL JOIN 톺아보기 **
1. SQL JOIN 톺아보기
1-1. 조인 알고 보면 쉬운…듯…?
DM 구축할 때 디멘션/팩트 테이블을 조인을 많이 할 수 밖에 없음.. 그래서 오늘 조인을 좀 더 공부해봤는데 WITH 문을 썻다면
쿼리가 좀 더 보기 좋았겠지만 일단 패쓰.. 아무튼 조인을 많이 하면 할수록 (특히 서브쿼리로 조인할수록) 쿼리문이 더러워짐..
WITH문만 익숙해지면 가독성 좋게 짤 수 있을 거 같음..
1-2. JOIN이란?
서로 다른 테이블의 공통된 컬럼(Key)을 기준으로 데이터를 연결하는 SQL 문법
왜 필요한가?
- 테이블마다 저장하는 정보가 다름 (ex. 트레이너 정보, 포켓몬 정보, 포획 기록)
- 분석에 필요한 정보가 여러 테이블에 흩어져 있음
- 데이터를 연결해서 하나의 결과 테이블로 만들어야 분석 가능함
2-1. 포켓몬 데이터로 JOIN 구조 이해해봅시다
사용 데이터 예시
트레이너 테이블 (trainer)
| id | name | age | hometown | preferred_pokemon_type | badge_count | achievement_level |
|---|---|---|---|---|---|---|
| 1 | Ash | 16 | Seoul | Electric | 8 | Master |
| 2 | K | 18 | Busan | Water | 6 | Advanced |
| 3 | Peter | 20 | Gwangju | Rock | 7 | Expert |
포켓몬 테이블 (pokemon)
| id | kor_name | type1 | type2 | total | hp | attack | defense | speed | generation | is_legendary |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 이상해씨 | Grass | Poison | 318 | 45 | 49 | 49 | 45 | 1 | FALSE |
| 4 | 파이리 | Fire | 309 | 39 | 52 | 43 | 65 | 1 | FALSE |
포획 기록 테이블 (trainer_pokemon)
| id | trainer_id | pokemon_id | catch_date | location |
|---|---|---|---|---|
| 1 | 1 | 1 | 2023-01-14 | Seoul |
| 2 | 2 | 4 | 2023-02-10 | Busan |
| 3 | 3 | 3 | 2023-03-05 | Incheon |
2-2. JOIN 관계도
trainer_pokemon을 기준으로trainer와pokemon을 각각 조인trainer_pokemon.trainer_id=trainer.idtrainer_pokemon.pokemon_id=pokemon.id
SQL 예시 (BigQuery 기준)
SELECT
tp.*,
t.name,
p.kor_name,
p.type1
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.trainer AS t
ON tp.trainer_id = t.id
LEFT JOIN basic.pokemon AS p
ON tp.pokemon_id = p.id;
3-1. 다양한 JOIN 종류
| JOIN 종류 | 설명 |
|---|---|
| INNER JOIN | 양쪽 테이블 모두 일치하는 값만 가져옴 |
| LEFT JOIN | 왼쪽 테이블 기준, 일치하지 않으면 NULL |
| RIGHT JOIN | 오른쪽 테이블 기준, 일치하지 않으면 NULL |
| FULL JOIN | 양쪽 테이블 전체를 가져오고, 일치 안 하면 NULL |
| CROSS JOIN | 두 테이블의 모든 행을 곱집합(Cartesian product) |
예시 비교 (A vs B)
| A.id | A.val | B.id | B.val |
|---|---|---|---|
| 1 | 가 | 1 | A |
| 2 | 나 | 2 | B |
| 3 | 다 | 4 | C |
INNER JOIN: id가 1, 2인 데이터만 연결됨LEFT JOIN: A 테이블 기준으로, B가 없으면 NULLRIGHT JOIN: B 테이블 기준으로, A가 없으면 NULLFULL JOIN: A, B 모두 포함. 없는 값은 NULLCROSS JOIN: 3*3 = 9개 행 생성
4-1. JOIN 쿼리 작성 흐름
- 테이블 확인: 어떤 데이터가 어디에 저장돼 있는지 파악
- 기준 테이블 선택: 가장 중심이 될 테이블 결정 (보통 fact 테이블)
- JOIN Key 찾기: 테이블 간 연결 기준이 될 컬럼 파악
- 결과 예상: 어떤 결과가 나올지 미리 손으로 써보기
- 쿼리 작성: LEFT JOIN을 먼저 써보는 걸 추천함
5-1. JOIN 실전 연습 문제
문제 1.
SELECT
p.kor_name,
COUNT(tp.id) AS pokemon_cnt
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.pokemon AS p
ON tp.pokemon_id = p.id
WHERE tp.status IN ('Active', 'Training')
GROUP BY p.kor_name;
- 트레이너가 보유한 포켓몬 수 구하기 (보유: status IN (‘Active’, ‘Training’))
문제 2.
SELECT
t.name,
COUNT(*) AS grass_pokemon_cnt
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.trainer AS t
ON tp.trainer_id = t.id
LEFT JOIN basic.pokemon AS p
ON tp.pokemon_id = p.id
WHERE p.type1 = 'Grass'
GROUP BY t.name;
- 트레이너별로 보유한 Grass 타입 포켓몬 수 구하기
문제 3.
SELECT
COUNT(DISTINCT tp.trainer_id) AS local_catcher_cnt
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.trainer AS t
ON tp.trainer_id = t.id
WHERE tp.location = t.hometown;
- 고향과 포획 지역이 같은 트레이너 수 구하기
문제 4.
SELECT
p.type1,
COUNT(*) AS cnt
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.trainer AS t
ON tp.trainer_id = t.id
LEFT JOIN basic.pokemon AS p
ON tp.pokemon_id = p.id
WHERE t.achievement_level = 'Master'
AND tp.status IN ('Active', 'Training')
GROUP BY p.type1
ORDER BY cnt DESC
LIMIT 1;
- Master 등급 트레이너가 가장 많이 보유한 타입은?
문제 5. Incheon 출신 트레이너가 보유한 세대별 포켓몬 수 (1, 2세대)
SELECT
p.generation,
COUNT(*) AS cnt
FROM basic.trainer_pokemon AS tp
LEFT JOIN basic.trainer AS t
ON tp.trainer_id = t.id
LEFT JOIN basic.pokemon AS p
ON tp.pokemon_id = p.id
WHERE t.hometown = 'Incheon'
AND p.generation IN (1, 2)
GROUP BY p.generation;
- Incheon 출신 트레이너가 보유한 세대별 포켓몬 수 (1, 2세대)
6-1. JOIN 작성 시 헷갈렸던 부분들 정리
- JOIN 종류 선택법 → LEFT JOIN 위주로 시작, INNER JOIN은 꼭 필요할 때만
- 왼쪽/오른쪽 기준 → 기준이 되는 테이블을 왼쪽에 두는 게 관행적이고 읽기 편함
- 여러 테이블 JOIN 가능? → 가능. 단, 각 JOIN이 어떤 결과를 만드는지 항상 중간 체크 필수
- 컬럼 다 선택해도 되나? → 처음엔 확인용으로 다 해도 OK. 다만 비용 줄이려면 필요한 컬럼만 선택할 것
- NULL 값이 뭐임? → 아무 값도 없는 상태. 0이나 빈 문자열과 다름.JOIN에서 값이 없으면 NULL로 표시됨
7-1. 마무리하며
JOIN은 SQL에서 가장 많이 쓰이는 기능 중 하나임. 처음엔 어렵지만, 테이블 구조를 이해하면 자연스럽게 익혀짐.
기준 테이블 설정 → 연결할 키 찾기 → 결과 예측 → 쿼리 작성 이 네 단계를 항상 기억해두자.
끄읕
댓글남기기