3 분 소요

[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 을 기준으로 trainerpokemon을 각각 조인
  • 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가 없으면 NULL
  • RIGHT JOIN: B 테이블 기준으로, A가 없으면 NULL
  • FULL JOIN: A, B 모두 포함. 없는 값은 NULL
  • CROSS JOIN: 3*3 = 9개 행 생성

4-1. JOIN 쿼리 작성 흐름

  1. 테이블 확인: 어떤 데이터가 어디에 저장돼 있는지 파악
  2. 기준 테이블 선택: 가장 중심이 될 테이블 결정 (보통 fact 테이블)
  3. JOIN Key 찾기: 테이블 간 연결 기준이 될 컬럼 파악
  4. 결과 예상: 어떤 결과가 나올지 미리 손으로 써보기
  5. 쿼리 작성: 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 작성 시 헷갈렸던 부분들 정리

  1. JOIN 종류 선택법 → LEFT JOIN 위주로 시작, INNER JOIN은 꼭 필요할 때만
  2. 왼쪽/오른쪽 기준 → 기준이 되는 테이블을 왼쪽에 두는 게 관행적이고 읽기 편함
  3. 여러 테이블 JOIN 가능? → 가능. 단, 각 JOIN이 어떤 결과를 만드는지 항상 중간 체크 필수
  4. 컬럼 다 선택해도 되나? → 처음엔 확인용으로 다 해도 OK. 다만 비용 줄이려면 필요한 컬럼만 선택할 것
  5. NULL 값이 뭐임? → 아무 값도 없는 상태. 0이나 빈 문자열과 다름.JOIN에서 값이 없으면 NULL로 표시됨

7-1. 마무리하며

JOIN은 SQL에서 가장 많이 쓰이는 기능 중 하나임. 처음엔 어렵지만, 테이블 구조를 이해하면 자연스럽게 익혀짐.

기준 테이블 설정 → 연결할 키 찾기 → 결과 예측 → 쿼리 작성 이 네 단계를 항상 기억해두자.

끄읕

태그:

카테고리:

업데이트:

댓글남기기