본문 바로가기
● Data Insights/SQL

SQL JOIN 가이드: 종류, 이론, 실전 예제와 최적화

by DATA Canvas 2025. 11. 3.
반응형

JOIN은 관계형 데이터베이스의 핵심 개념으로, 다음과 같은 이유로 필수적입니다

  • 정규화된 데이터 구조: 데이터 중복을 피하기 위해 분리된 테이블들을 논리적으로 연결
  • 데이터 무결성 보장: 외래키 관계를 통한 일관성 있는 데이터 관리
  • 복합 정보 조회: 단일 테이블로는 불가능한 복잡한 비즈니스 인사이트 도출
  • 저장공간 효율성: 중복 데이터 제거로 인한 디스크 사용량 최적화


JOIN의 모든 종류와 이론

1. INNER JOIN (내부 조인)

이론: 두 테이블의 교집합을 반환하며, 조인 조건을 만족하는 행만 결합합니다.

로직:

  • 왼쪽 테이블의 각 행에 대해 오른쪽 테이블에서 일치하는 행을 찾음
  • 일치하는 행이 없으면 결과에서 제외

유의사항:

  • 데이터 손실 가능성: 조인 조건을 만족하지 않는 행들이 결과에서 누락됨

해결방안:

  • 조인 전 데이터 존재 여부 확인
  • LEFT JOIN으로 대체하여 데이터 누락 방지 검토

쿼리 예시:

-- 주문이 있는 고객만 조회
SELECT c.customer_id, c.name, o.order_date, o.amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;

해석: customers와 orders 테이블에서 customer_id가 일치하는 데이터만 반환하므로, 주문 이력이 없는 고객은 결과에 나타나지 않습니다.

반응형

2. LEFT (OUTER) JOIN (왼쪽 외부 조인)

이론: 왼쪽 테이블을 기준으로 모든 데이터를 반환하고, 오른쪽 테이블에 일치하는 데이터가 없으면 NULL로 채웁니다.

로직:

  • 왼쪽 테이블의 모든 행 보존
  • 오른쪽 테이블에서 매칭되지 않는 컬럼은 NULL 처리

유의사항:

  • 1:N 관계에서 데이터 뻥튀기 발생: 왼쪽 테이블 하나의 행이 오른쪽 테이블의 여러 행과 매칭될 때 중복 발생

해결방안:

-- 1) DISTINCT 사용 (데이터량이 적을 때)
SELECT DISTINCT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

-- 2) GROUP BY 사용 (성능이 더 좋음)
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;

-- 3) 조인 전 중복 제거 (서브쿼리 활용)
SELECT c.customer_id, c.name, o.latest_order
FROM customers c
LEFT JOIN (
    SELECT customer_id, MAX(order_date) as latest_order
    FROM orders 
    GROUP BY customer_id
) o ON c.customer_id = o.customer_id;

쿼리 예시:

-- 모든 고객과 주문 정보 조회 (주문이 없는 고객도 포함)
SELECT c.customer_id, c.name, 
       ISNULL(SUM(o.amount), 0) as total_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;

해석: 모든 고객이 결과에 포함되며, 주문이 없는 고객의 total_amount는 0으로 표시됩니다.


3. RIGHT (OUTER) JOIN (오른쪽 외부 조인)

이론: 오른쪽 테이블을 기준으로 모든 데이터를 반환하며, LEFT JOIN의 반대 개념입니다.

로직: LEFT JOIN과 동일하나 기준 테이블이 반대

유의사항:

  • 실무에서 거의 사용하지 않음 (LEFT JOIN으로 대체 가능)
  • 가독성 저하로 인한 유지보수 어려움

해결방안:

  • LEFT JOIN으로 테이블 순서를 바꿔서 작성 권장

쿼리 예시:

-- RIGHT JOIN 사용 (권장하지 않음)
SELECT c.customer_id, c.name, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;

-- LEFT JOIN으로 대체 (권장)
SELECT c.customer_id, c.name, o.order_date
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id;

4. FULL OUTER JOIN (완전 외부 조인)

이론: 두 테이블의 합집합을 반환하며, 양쪽 테이블의 모든 데이터를 포함합니다.

로직:

  • 두 테이블에서 조인 조건에 일치하는 행은 결합
  • 일치하지 않는 행들도 모두 포함 (NULL로 채움)

유의사항:

  • 성능 이슈: 대용량 데이터에서 매우 느림
  • 메모리 사용량 증가: 두 테이블의 모든 데이터를 메모리에 로드

해결방안:

  • UNION 연산자로 대체 고려
  • 인덱스 최적화 필수
  • 파티셔닝 적용 검토

쿼리 예시:

-- 고객과 주문 데이터의 모든 정보 조회
SELECT 
    ISNULL(c.customer_id, o.customer_id) as customer_id,
    c.name,
    o.order_date,
    o.amount
FROM customers c
FULL OUTER JOIN orders o ON c.customer_id = o.customer_id;

해석: 고객 정보가 없는 주문과 주문이 없는 고객 모두 결과에 포함됩니다.


5. CROSS JOIN (교차 조인)

이론: 카티션 곱(Cartesian Product)을 생성하여 두 테이블의 모든 행 조합을 반환합니다.

로직:

  • 첫 번째 테이블의 각 행을 두 번째 테이블의 모든 행과 결합
  • 결과 행 수 = 첫 번째 테이블 행 수 × 두 번째 테이블 행 수

유의사항:

  • 기하급수적 데이터 증가: 매우 위험한 성능 저하 유발
  • 의도치 않은 CROSS JOIN: WHERE 절 누락으로 발생 가능

해결방안:

  • 반드시 적절한 WHERE 조건 추가
  • 작은 테이블에서만 사용
  • 실행 전 예상 결과 행 수 계산

쿼리 예시:

-- 모든 제품과 카테고리 조합 생성 (주의 필요)
SELECT p.product_name, c.category_name
FROM products p
CROSS JOIN categories c;

-- 실제 비즈니스에서는 조건 추가 필수
SELECT p.product_name, c.category_name
FROM products p
CROSS JOIN categories c
WHERE p.price > 1000
  AND c.active_yn = 'Y';

해석: 모든 제품과 카테고리의 조합을 생성하므로, 제품 100개와 카테고리 10개가 있다면 1,000개의 결과가 생성됩니다.


6. SELF JOIN (자체 조인)

이론: 동일한 테이블을 자기 자신과 조인하여 계층적 관계나 같은 테이블 내 데이터 비교에 사용합니다.

로직:

  • 테이블 별칭을 다르게 지정하여 마치 두 개의 다른 테이블처럼 처리
  • 주로 부모-자식 관계나 동일 레벨 비교에 활용

유의사항:

  • 복잡성 증가: 동일 테이블의 다른 별칭으로 인한 혼동 가능
  • 성능 영향: 인덱스가 없으면 전체 테이블 스캔 두 번 발생

해결방안:

  • 명확한 별칭 사용 (a, b 대신 emp, mgr 등)
  • 조인 조건 컬럼에 인덱스 생성
  • 계층 쿼리 대안 검토 (CTE 등)

쿼리 예시:

-- 직원과 관리자 정보 조회
SELECT 
    emp.employee_id,
    emp.name as employee_name,
    mgr.name as manager_name
FROM employees emp
LEFT JOIN employees mgr ON emp.manager_id = mgr.employee_id;

-- 같은 부서 내 급여 비교
SELECT 
    e1.name as employee1,
    e2.name as employee2,
    e1.salary,
    e2.salary
FROM employees e1
JOIN employees e2 ON e1.department_id = e2.department_id
WHERE e1.employee_id < e2.employee_id  -- 중복 조합 방지
  AND e1.salary > e2.salary;

해석: 첫 번째 예시는 각 직원과 그의 관리자를 보여주고, 두 번째는 같은 부서에서 더 높은 급여를 받는 동료들을 비교합니다.


7. APPLY 연산자 (MSSQL 특화)

CROSS APPLYOUTER APPLY는 MSSQL의 고급 조인 기능입니다.

CROSS APPLY:

  • 테이블 함수나 상관 서브쿼리와 조인
  • INNER JOIN과 유사하나 더 동적

OUTER APPLY:

  • LEFT JOIN과 유사하나 테이블 함수 결과가 없어도 왼쪽 테이블 행 반환

쿼리 예시:

-- 각 고객의 최근 3개 주문 조회
SELECT c.customer_id, c.name, recent_orders.*
FROM customers c
CROSS APPLY (
    SELECT TOP 3 order_date, amount
    FROM orders o
    WHERE o.customer_id = c.customer_id
    ORDER BY order_date DESC
) recent_orders;

JOIN 성능 최적화와 모범 사례

인덱스 활용 전략

조인 성능의 핵심은 적절한 인덱스 설계입니다

-- 조인 조건 컬럼에 인덱스 생성
CREATE INDEX IX_orders_customer_id ON orders(customer_id);
CREATE INDEX IX_customers_id ON customers(customer_id);

-- 복합 인덱스 활용
CREATE INDEX IX_orders_customer_date 
ON orders(customer_id, order_date DESC);

실행 계획 분석

조인 알고리즘 이해

  1. Nested Loop Join: 작은 테이블 + 인덱스 있는 큰 테이블에 최적
  2. Hash Join: 대용량 테이블 간 조인에 적합
  3. Merge Join: 정렬된 데이터 조인에 효율적
-- MSSQL 실행 계획 확인
SET SHOWPLAN_ALL ON;
SELECT c.name, COUNT(o.order_id)
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;
SET SHOWPLAN_ALL OFF;

조인 순서 최적화

작은 테이블을 먼저 조인하는 것이 일반적으로 효율적입니다

-- 비효율적: 큰 테이블부터 조인
SELECT *
FROM large_table lt
JOIN small_filtered sf ON lt.id = sf.id
WHERE sf.status = 'ACTIVE';

-- 효율적: 작은 테이블 먼저 필터링 후 조인
SELECT *
FROM (
    SELECT * FROM small_table WHERE status = 'ACTIVE'
) sf
JOIN large_table lt ON sf.id = lt.id;

MSSQL 특화 힌트 활용

-- Loop Join 힌트 (작은 데이터셋)
SELECT *
FROM customers c
INNER LOOP JOIN orders o ON c.customer_id = o.customer_id;

-- Hash Join 힌트 (대용량 데이터)
SELECT *
FROM customers c  
INNER HASH JOIN orders o ON c.customer_id = o.customer_id;

-- Merge Join 힌트 (정렬된 데이터)
SELECT *
FROM customers c
INNER MERGE JOIN orders o ON c.customer_id = o.customer_id;

일반적인 최적화 팁

  1. SELECT * 지양: 필요한 컬럼만 선택하여 네트워크 부하 감소
  2. WHERE 절 최적화: 조인 전 데이터 필터링으로 조인 대상 축소
  3. 임시 테이블 활용: 복잡한 다중 조인 시 중간 결과 저장
  4. 통계 정보 갱신: 쿼리 옵티마이저의 정확한 판단을 위한 통계 관리

JOIN은 SQL의 가장 강력한 기능 중 하나이지만, 올바른 이해와 적절한 최적화 없이는 심각한 성능 문제를 야기할 수 있습니다. 각 JOIN 유형의 특성을 파악하고, 데이터의 성격과 비즈니스 요구사항에 맞는 최적의 조인 전략을 선택하는 것이 중요합니다.

반응형