SQL JOIN
SQL JOIN은 두 개 이상의 테이블을 하나의 결과로 합치는 방법입니다. USERS와 ORDERS처럼 외래키로 연결된 테이블이 있을 때, 어느 쪽 데이터를 기준으로 얼마나 남길지에 따라 INNER / LEFT / RIGHT / FULL 네 가지 방식으로 나뉩니다. 벤 다이어그램으로 생각하면 이해하기 쉽습니다(다만 비유일 뿐입니다) — 두 원 중 어느 부분을 결과로 남기느냐의 차이일 뿐입니다.
핵심 개념
INNER JOIN
양쪽 테이블 모두에 값이 있는 행만 남깁니다. 벤 다이어그램에서 두 원이 겹치는 부분(교집합)입니다.
핵심 개념
LEFT JOIN
왼쪽(A) 테이블의 모든 행을 남기고, 짝이 없는 오른쪽(B) 값은 NULL로 채웁니다. 벤 다이어그램에서 왼쪽 원 전체입니다.
핵심 개념
RIGHT JOIN
오른쪽(B) 테이블의 모든 행을 남기고, 짝이 없는 왼쪽(A) 값은 NULL로 채웁니다. 벤 다이어그램에서 오른쪽 원 전체입니다.
핵심 개념
FULL OUTER JOIN
양쪽 테이블의 모든 행을 남기고, 짝이 없는 쪽은 NULL로 채웁니다. 벤 다이어그램에서 두 원의 합집합입니다.
핵심 개념
JOIN 조건
보통 외래키 = 기본키 형태로 조건을 겁니다. 예: ORDERS.user_id = USERS.id. 이 조건에 맞는 행끼리만 짝지어집니다.
핵심 개념
왜 NULL이 나올까
OUTER JOIN(LEFT/RIGHT/FULL)에서 짝이 없는 행도 결과에 포함시키다 보니, 상대 테이블 쪽 컬럼을 채울 값이 없어 NULL이 들어갑니다.
UNION으로 합쳐서 흉내 냅니다.
JOIN이 하는 일
개념적으로 JOIN은 두 테이블의 모든 행 조합(곱집합)을 만든 뒤 JOIN 조건에 맞는 조합만 남기는 연산입니다. 그 위에서 OUTER JOIN은 조건에 맞는 짝이 없는 행도 NULL로 채워 남깁니다. 실제로 DB가 곱집합을 다 만들지는 않고 해시나 인덱스 같은 방법으로 같은 결과를 더 빠르게 계산합니다. JOIN ... ON 형태의 명시적 구문은 SQL-92에서 도입되었다고 알려져 있습니다.
숫자로 확인하기
자주 하는 오해
NULL = NULL은 참이 아니라 알 수 없음이어서, 조인 키가 NULL인 행은 서로 짝지어지지 않습니다.
SELECT *의 열 순서는 달라집니다). 그래서 LEFT JOIN만 쓰는 스타일도 많습니다.
면접에서 이렇게 물어요
면접 질문
INNER JOIN과 OUTER JOIN의 차이는 무엇인가요?
INNER JOIN은 양쪽에 짝이 있는 행만 남깁니다. OUTER JOIN은 한쪽(LEFT, RIGHT) 또는 양쪽(FULL)의 모든 행을 남기고, 짝이 없으면 상대 컬럼을 NULL로 채웁니다.
면접 질문
ON과 WHERE의 차이는 무엇인가요?
ON은 어떤 행끼리 짝지을지 정하는 조건이고, WHERE는 만들어진 결과를 거르는 조건입니다. INNER JOIN에서는 결과가 같지만 OUTER JOIN에서는 NULL로 채워진 행의 유지 여부가 달라집니다.
면접 질문
주문이 한 건도 없는 사용자를 어떻게 찾나요?
USERS LEFT JOIN ORDERS로 사용자를 모두 남긴 뒤, 오른쪽 기본키가 NULL인 행만 WHERE로 고릅니다. 짝이 없는 행만 남는 anti-join 패턴입니다.
면접 질문
DB는 JOIN을 어떻게 실행하나요?
중첩 루프, 해시, 병합(정렬 후 합치기) 방식이 대표적이고, 옵티마이저가 테이블 크기와 인덱스를 보고 비용이 낮은 방법을 고릅니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
SQL — INNER / LEFT / RIGHT JOIN
-- INNER JOIN: 양쪽 모두 값이 있는 행만 SELECT u.id, u.name, o.id AS order_id, o.product FROM users u INNER JOIN orders o ON o.user_id = u.id; -- LEFT JOIN: USERS 전부 + 짝 없는 ORDERS는 NULL SELECT u.id, u.name, o.id AS order_id, o.product FROM users u LEFT JOIN orders o ON o.user_id = u.id; -- RIGHT JOIN: ORDERS 전부 + 짝 없는 USERS는 NULL SELECT u.id, u.name, o.id AS order_id, o.product FROM users u RIGHT JOIN orders o ON o.user_id = u.id;
세 쿼리의 FROM/ON 절은 완전히 동일하고, JOIN 키워드만 다릅니다 — 시각화의 벤 다이어그램에서 강조 영역이 바뀌는 것과 같은 지점입니다.
LEFT JOIN 결과에서 박도윤처럼 주문이 없는 사용자는 order_id, product가 NULL로 나옵니다.
Java — JPQL에서 LEFT JOIN FETCH로 N+1 방지
public interface UserRepository extends JpaRepository<User, Long> { // LEFT JOIN FETCH: 주문이 없는 사용자도 결과에 포함(= LEFT JOIN), // 연관된 Order를 한 번에 가져와 LazyInitializationException도 방지 @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders") List<User> findAllWithOrders(); }
이 프로젝트 규칙상 연관 엔티티를 조회할 땐 항상 LEFT JOIN FETCH를 씁니다 — INNER JOIN FETCH를 쓰면 주문이 없는 User(박도윤)가 결과에서 통째로 빠져버리기 때문입니다.
즉 "연관 데이터를 함께 가져오고 싶다"는 이유만으로도, 의도치 않게 INNER JOIN의 필터링 효과가 발생할 수 있다는 점을 이 시각화의 INNER 탭과 비교해서 기억해두면 좋습니다.
SQL — MySQL에서 FULL OUTER JOIN 흉내내기
-- MySQL은 FULL OUTER JOIN 문법이 없어서 UNION으로 대체 SELECT u.id, u.name, o.id AS order_id, o.product FROM users u LEFT JOIN orders o ON o.user_id = u.id UNION SELECT u.id, u.name, o.id AS order_id, o.product FROM users u RIGHT JOIN orders o ON o.user_id = u.id;
LEFT JOIN 결과(USERS 전부)와 RIGHT JOIN 결과(ORDERS 전부)를 UNION으로 합치면, 겹치는 행은 자동으로 한 번만 남고 양쪽의 unmatched 행은 모두 살아남습니다.
이 시각화의 FULL 탭에서 4행(박도윤의 NULL 행 + user_id=5 주문의 NULL 행 포함)이 나오는 것과 정확히 같은 결과입니다.
Q1
INNER JOIN의 결과는?
Q2
LEFT JOIN에서 오른쪽 테이블에 일치하는 행이 없을 때 결과는?
Q3
FULL OUTER JOIN의 결과는?