SQL 조인은 관계형 데이터베이스의 핵심입니다. 관련 열을 기반으로 두 개 이상의 테이블에서 데이터를 결합할 수 있습니다. 기본적인 개념임에도 많은 개발자에게 가장 혼란스러운 주제 중 하나입니다. 이 가이드는 시각적 다이어그램과 실용적인 예제를 사용하여 각 조인 유형을 명확하게 설명합니다.
샘플 데이터: 두 개의 테이블
이 가이드에서는 다음 두 개의 간단한 테이블을 사용합니다:
-- employees table
+----+----------+---------------+
| id | name | department_id |
+----+----------+---------------+
| 1 | Alice | 1 |
| 2 | Bob | 2 |
| 3 | Charlie | 1 |
| 4 | Diana | NULL |
+----+----------+---------------+
-- departments table
+----+-------------+
| id | dept_name |
+----+-------------+
| 1 | Engineering |
| 2 | Marketing |
| 3 | Sales |
+----+-------------+INNER JOIN (내부 조인)
두 테이블 모두에서 일치하는 값이 있는 행만 반환합니다. 두 집합의 교집합으로 생각하세요.
Table A Table B
+---------+ +---------+
| | | |
| +----+-----+----+ |
| | INNER JOIN | |
| +----+-----+----+ |
| | | |
+---------+ +---------+SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;결과:
+----------+-------------+
| name | dept_name |
+----------+-------------+
| Alice | Engineering |
| Bob | Marketing |
| Charlie | Engineering |
+----------+-------------+
-- Diana excluded (department_id is NULL)
-- Sales excluded (no employee in Sales)두 테이블 모두에 존재하는 레코드만 필요할 때 사용합니다.
LEFT JOIN (왼쪽 외부 조인)
왼쪽 테이블의 모든 행과 오른쪽 테이블의 일치하는 행을 반환합니다. 일치하지 않으면 NULL이 반환됩니다.
Table A Table B
+---------+ +---------+
|XXXXXXXXX| | |
|XXXX+----+-----+----+ |
|XXXX| LEFT JOIN XXXX| |
|XXXX+----+-----+----+ |
|XXXXXXXXX| | |
+---------+ +---------+
(all of A + matching B)SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;결과:
+----------+-------------+
| name | dept_name |
+----------+-------------+
| Alice | Engineering |
| Bob | Marketing |
| Charlie | Engineering |
| Diana | NULL |
+----------+-------------+
-- Diana included with NULL dept_name일치 여부와 관계없이 왼쪽 테이블의 모든 레코드가 필요할 때 사용합니다.
RIGHT JOIN (오른쪽 외부 조인)
오른쪽 테이블의 모든 행과 왼쪽 테이블의 일치하는 행을 반환합니다.
Table A Table B
+---------+ +---------+
| | |XXXXXXXXX|
| +----+-----+XXXX+XXXX|
| |XXXX RIGHT JOINXXXX|
| +----+-----+XXXX+XXXX|
| | |XXXXXXXXX|
+---------+ +---------+
(matching A + all of B)SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;결과:
+----------+-------------+
| name | dept_name |
+----------+-------------+
| Alice | Engineering |
| Charlie | Engineering |
| Bob | Marketing |
| NULL | Sales |
+----------+-------------+
-- Sales included with NULL name오른쪽 테이블의 모든 레코드가 필요할 때 사용합니다.
FULL OUTER JOIN (완전 외부 조인)
두 테이블의 모든 행을 반환합니다. 일치하지 않는 곳은 NULL로 채웁니다.
Table A Table B
+---------+ +---------+
|XXXXXXXXX| |XXXXXXXXX|
|XXXX+----+-----+XXXX+XXXX|
|XXXX|FULL OUTER JOINXXXX|
|XXXX+----+-----+XXXX+XXXX|
|XXXXXXXXX| |XXXXXXXXX|
+---------+ +---------+
(everything from both)SELECT e.name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;결과:
+----------+-------------+
| name | dept_name |
+----------+-------------+
| Alice | Engineering |
| Bob | Marketing |
| Charlie | Engineering |
| Diana | NULL |
| NULL | Sales |
+----------+-------------+두 테이블의 모든 레코드가 필요할 때 사용합니다.
CROSS JOIN (교차 조인)
두 테이블의 카르테시안 곱을 반환합니다. ON 절이 필요 없습니다.
SELECT e.name, d.dept_name
FROM employees e
CROSS JOIN departments d;
-- Returns 4 × 3 = 12 rows결과:
+----------+-------------+
| name | dept_name |
+----------+-------------+
| Alice | Engineering |
| Alice | Marketing |
| Alice | Sales |
| Bob | Engineering |
| Bob | Marketing |
| Bob | Sales |
| Charlie | Engineering |
| Charlie | Marketing |
| Charlie | Sales |
| Diana | Engineering |
| Diana | Marketing |
| Diana | Sales |
+----------+-------------+조합 생성, 테스트 데이터, 캘린더 그리드에 사용합니다.
셀프 조인 (Self JOIN)
테이블을 자기 자신과 조인합니다. 계층 데이터나 같은 테이블 내 행 비교에 유용합니다.
-- employees_v2 with manager_id column
+----+---------+------------+
| id | name | manager_id |
+----+---------+------------+
| 1 | Alice | NULL |
| 2 | Bob | 1 |
| 3 | Charlie | 1 |
| 4 | Diana | 2 |
+----+---------+------------+
SELECT
e.name AS employee,
m.name AS manager
FROM employees_v2 e
LEFT JOIN employees_v2 m ON e.manager_id = m.id;결과:
+----------+---------+
| employee | manager |
+----------+---------+
| Alice | NULL |
| Bob | Alice |
| Charlie | Alice |
| Diana | Bob |
+----------+---------+직원-관리자 관계, 계층적 카테고리, 행 비교에 사용합니다.
어떤 조인을 사용할까? 빠른 참조
| 조인 유형 | 반환 내용 | 최적 사용 사례 |
|---|---|---|
| INNER JOIN | A ∩ B | 일치 데이터만 |
| LEFT JOIN | A + (A ∩ B) | 왼쪽 전체 + 일치 |
| RIGHT JOIN | (A ∩ B) + B | 오른쪽 전체 + 일치 |
| FULL OUTER JOIN | A ∪ B | 두 테이블 전체 |
| CROSS JOIN | A × B | 모든 조합 생성 |
| Self JOIN | 조인 유형에 따라 | 계층/자기참조 데이터 |
성능 팁
- JOIN 열에는 항상 인덱스를 생성하세요.
- 프로덕션에서는 SELECT *를 피하고 필요한 열만 선택하세요.
- 가능하면 작은 테이블을 FROM 절 앞에 배치하세요.
- EXPLAIN ANALYZE로 쿼리 실행 계획을 확인하세요.
- 상관 서브쿼리에서는 IN보다 EXISTS를 사용하세요.
- 표현식이나 함수에서의 조인을 피하세요 (인덱스 사용 불가).
-- Create index on JOIN column
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Use EXPLAIN to check query plan
EXPLAIN ANALYZE
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;흔한 실수
| 실수 | 문제 | 해결 |
|---|---|---|
| Using SELECT * | Returns unnecessary columns, wastes bandwidth | Select only needed columns |
| Missing index on JOIN column | Full table scan, extremely slow | CREATE INDEX |
| NULL comparison trap | NULL = NULL evaluates to FALSE | Use IS NULL or COALESCE |
| Accidental Cartesian product | Missing ON condition, row explosion | Always verify ON clause |
| Filtering OUTER JOIN in WHERE | WHERE filter turns LEFT JOIN into INNER JOIN | Put conditions in ON clause |
| Unnecessary DISTINCT | Hides incorrect JOIN logic | Fix JOIN condition, don't add DISTINCT |
| Joining on functions | Index unusable, forces full scan | Store normalized data or use computed columns |
-- ❌ Wrong: WHERE turns LEFT JOIN into INNER JOIN
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.dept_name = 'Engineering'; -- filters out NULLs!
-- ✅ Correct: Put condition in ON clause
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.id
AND d.dept_name = 'Engineering'; -- preserves NULLs자주 묻는 질문
INNER JOIN과 OUTER JOIN의 차이는?
INNER JOIN은 일치하는 행만 반환합니다. OUTER JOIN은 일치하지 않는 행도 포함하며 NULL로 채웁니다.
3개 이상의 테이블을 조인할 수 있나요?
네. 여러 JOIN을 체이닝할 수 있습니다. 실질적인 제한은 없습니다.
INNER JOIN에서 ON 절을 빠뜨리면?
대부분의 SQL에서 구문 오류가 발생합니다. 일부에서는 CROSS JOIN이 될 수 있습니다.
LEFT JOIN과 LEFT OUTER JOIN은 같나요?
네. OUTER 키워드는 선택 사항입니다. 완전히 동일합니다.
어떤 조인이 가장 빠른가요?
INNER JOIN이 일반적으로 가장 빠릅니다. 하지만 실제 성능은 인덱스, 테이블 크기, 옵티마이저에 따라 다릅니다.
NATURAL JOIN이란?
같은 이름의 열에서 자동으로 조인합니다. 열 추가 시 동작이 변경될 수 있어 프로덕션에서는 비권장입니다.