SQL JOIN 종류 완벽 정리: INNER LEFT RIGHT FULL OUTER 차이점과 Self Join 활용법

SQL JOIN을 사용할 때 INNER와 LEFT의 차이를 정확히 이해하지 못해 의도와 다른 결과가 나온 경험이 있다면, 이 글이 필요합니다. JOIN 종류별 동작 원리와 실무에서 자주 마주치는 Self Join까지, 쿼리 결과를 예측 가능하게 만드는 핵심 개념을 정리했습니다. NULL 처리 규칙별칭(Alias) 사용법을 이해하면, 복잡한 다중 테이블 조인도 논리적으로 설계할 수 있습니다.


SQL JOIN 종류 완벽 정리: INNER LEFT RIGHT FULL OUTER 차이점과 Self Join 활용법




1. JOIN의 기본 개념과 종류

JOIN은 서로 다른(또는 같은) 테이블의 관련 열을 기준으로 데이터를 결합하는 SQL 문법입니다. 관계형 데이터베이스에서 정규화된 테이블을 연결해 의미 있는 결과를 만들 때 필수적으로 사용됩니다.

  • INNER JOIN: 양쪽 테이블에 모두 존재하는 행만 반환 (교집합)
  • LEFT JOIN (LEFT OUTER JOIN): 왼쪽 테이블의 모든 행을 보존하고, 매칭되지 않는 오른쪽 열은 NULL로 채움
  • RIGHT JOIN (RIGHT OUTER JOIN): 오른쪽 테이블의 모든 행을 보존하고, 매칭되지 않는 왼쪽 열은 NULL로 채움
  • FULL OUTER JOIN: 양쪽 테이블의 모든 행을 보존하며, 매칭되지 않는 부분은 NULL 처리

실무 팁: MySQL은 FULL OUTER JOIN을 직접 지원하지 않으므로, LEFT JOIN과 RIGHT JOIN을 UNION으로 결합해 구현해야 합니다. PostgreSQL이나 SQL Server를 사용한다면 FULL OUTER JOIN을 바로 사용할 수 있습니다.

2. INNER JOIN vs LEFT JOIN 실전 예제

고객(customer)과 도시(city) 테이블을 연결하는 경우를 살펴보겠습니다.

-- INNER JOIN 예제
SELECT 
    c1.customer_id,
    c1.firstname,
    c1.lastname,
    c2.name AS city_name
FROM customer AS c1
INNER JOIN city AS c2 
    ON c1.city_id = c2.city_id;

위 쿼리는 city_id가 양쪽 테이블에 모두 존재하는 고객만 반환합니다. 만약 customer 테이블에 city_id가 NULL인 행이 있다면 결과에서 제외됩니다.

-- LEFT JOIN 예제
SELECT 
    c1.customer_id,
    c1.firstname,
    c1.city_id,
    c2.name AS city_name
FROM customer AS c1
LEFT JOIN city AS c2 
    ON c1.city_id = c2.city_id;

LEFT JOIN은 모든 고객을 포함하되, 매칭되는 도시가 없으면 city_name에 NULL이 들어갑니다. 이 차이를 이해하지 못하면 데이터 누락이나 중복 집계 오류가 발생할 수 있습니다.

주의사항: WHERE 절에서 RIGHT 테이블의 컬럼을 조건으로 사용하면, LEFT JOIN이 사실상 INNER JOIN처럼 동작합니다. 예를 들어 WHERE c2.name IS NOT NULL을 추가하면 NULL 행이 필터링되어 LEFT JOIN의 의미가 사라집니다.

3. Self Join: 같은 테이블을 두 번 사용하는 기법

Self Join은 같은 테이블을 복사한 것처럼 두 번 사용해 서로 다른 행을 매칭하는 방식입니다. SQL에는 'Self Join'이라는 별도 키워드가 없으며, 별칭(Alias)으로 구분합니다.

-- Self Join 예제: 배우자 정보 조회
SELECT 
    c.customer_id,
    c.firstname AS customer_name,
    s.firstname AS spouse_name,
    s.lastname AS spouse_lastname
FROM customer AS c
LEFT JOIN customer AS s 
    ON c.spouse_id = s.customer_id;

위 쿼리는 customer 테이블을 c(본인)와 s(배우자)라는 별칭으로 두 번 참조합니다. c.spouse_id와 s.customer_id를 매칭해 배우자 정보를 결과에 추가합니다.

  • 반드시 별칭을 부여: 같은 테이블을 두 번 쓴다고 SQL이 자동으로 구분하지 못하므로, 서로 다른 별칭(AS c, AS s)을 명시해야 합니다.
  • ON 조건 정확성: c.spouse_id = s.customer_id처럼 관계를 명확히 정의하지 않으면 카티션 곱(Cartesian Product)이 발생해 성능이 급격히 저하됩니다.
  • NULL 확인: LEFT JOIN을 사용했으므로 배우자가 없는 고객은 spouse_name이 NULL로 표시됩니다.

실무 활용 사례: 조직도에서 상사-부하 관계, 가족 관계(부모-자녀), 추천인 트리 구조 등 동일 엔터티 내부의 계층 관계를 표현할 때 Self Join을 사용합니다.

4. JOIN 성능 최적화 체크리스트

JOIN 쿼리의 성능은 데이터 양과 인덱스 설계에 직접적인 영향을 받습니다.

  1. ON 절 컬럼에 인덱스 생성: JOIN 조건에 사용되는 컬럼(예: city_id, spouse_id)은 반드시 인덱스를 걸어야 합니다.
  2. WHERE 절 먼저 적용: JOIN 전에 WHERE로 데이터를 최대한 줄이면 조합 수가 감소합니다.
  3. SELECT 컬럼 최소화: SELECT *는 불필요한 데이터 전송을 유발하므로, 필요한 컬럼만 명시하십시오.
  4. EXPLAIN으로 실행 계획 확인: 쿼리 앞에 EXPLAIN을 붙여 인덱스 사용 여부와 JOIN 순서를 점검합니다.
-- 인덱스 생성 예제
CREATE INDEX idx_city_id ON customer(city_id);
CREATE INDEX idx_spouse_id ON customer(spouse_id);

3개 이상 테이블 조인 시: 가장 큰 테이블을 FROM 절에 두고, 작은 테이블을 순서대로 JOIN하는 것이 일반적이지만, 최신 DBMS의 쿼리 옵티마이저는 자동으로 최적 순서를 재배치합니다. 따라서 인덱스 설계와 WHERE 조건 배치가 더 중요합니다.

결론 및 실행 항목

INNER JOIN은 교집합만 반환하고, LEFT/RIGHT/FULL JOIN은 한쪽 또는 양쪽 데이터를 보존하며 NULL로 채웁니다. Self Join은 같은 테이블을 별칭으로 구분해 계층 관계를 표현할 때 사용하며, 별칭 부여와 ON 조건 정확성이 핵심입니다. 지금 당장 실행할 항목: 현재 프로젝트의 JOIN 쿼리에 EXPLAIN을 붙여 실행 계획을 확인하고, ON 절에 사용된 컬럼에 인덱스가 있는지 점검하십시오. 인덱스가 없다면 CREATE INDEX로 추가하고, 쿼리 실행 시간을 측정해 개선 효과를 수치로 확인하십시오.


#함께 읽으면 좋은 글

MySQL Too many connections 에러 해결 완벽 가이드 max_connections 설정부터 커넥션 풀 최적화까지 : 바로보기

댓글