여러 테이블을 결합하여 데이터를 조회하는 JOIN의 종류와 사용법을 정리합니다.

샘플 데이터 준비

CREATE TABLE departments (
    id    INT PRIMARY KEY AUTO_INCREMENT,
    name  VARCHAR(30) NOT NULL
);

CREATE TABLE employees (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    name     VARCHAR(50) NOT NULL,
    dept_id  INT,
    salary   DECIMAL(10,2),
    FOREIGN KEY (dept_id) REFERENCES departments(id)
);

INSERT INTO departments (name) VALUES ('개발팀'), ('기획팀'), ('디자인팀'), ('인사팀');

INSERT INTO employees (name, dept_id, salary) VALUES
('홍길동', 1, 5000000),
('김철수', 2, 4500000),
('이영희', 1, 5500000),
('박민수', 3, 4800000),
('최지은', NULL, 4000000);  -- 부서 미배정

JOIN 종류 한눈에 보기

JOIN 종류 설명
INNER JOIN 양쪽 테이블에 매칭되는 행만 반환
LEFT JOIN 왼쪽 테이블 전체 + 오른쪽 매칭 (없으면 NULL)
RIGHT JOIN 오른쪽 테이블 전체 + 왼쪽 매칭 (없으면 NULL)
CROSS JOIN 양쪽 테이블의 모든 조합 (카테시안 곱)
SELF JOIN 같은 테이블을 자기 자신과 결합

INNER JOIN

양쪽 테이블에 모두 매칭되는 데이터만 조회합니다.

SELECT e.name AS 직원명, d.name AS 부서명, e.salary
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;

결과: 최지은은 dept_id가 NULL이므로 결과에 포함되지 않습니다.

+--------+--------+---------+
| 직원명  | 부서명  | salary  |
+--------+--------+---------+
| 홍길동  | 개발팀  | 5000000 |
| 김철수  | 기획팀  | 4500000 |
| 이영희  | 개발팀  | 5500000 |
| 박민수  | 디자인팀 | 4800000 |
+--------+--------+---------+

LEFT JOIN (LEFT OUTER JOIN)

왼쪽 테이블의 모든 행을 반환하고, 오른쪽에 매칭이 없으면 NULL로 채웁니다.

SELECT e.name AS 직원명, d.name AS 부서명, e.salary
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
+--------+--------+---------+
| 직원명  | 부서명  | salary  |
+--------+--------+---------+
| 홍길동  | 개발팀  | 5000000 |
| 김철수  | 기획팀  | 4500000 |
| 이영희  | 개발팀  | 5500000 |
| 박민수  | 디자인팀 | 4800000 |
| 최지은  | NULL   | 4000000 |
+--------+--------+---------+

매칭 안 되는 행만 조회 (LEFT ONLY)

-- 부서가 배정되지 않은 직원
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.id IS NULL;

RIGHT JOIN (RIGHT OUTER JOIN)

오른쪽 테이블의 모든 행을 반환합니다.

SELECT e.name AS 직원명, d.name AS 부서명
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;
+--------+--------+
| 직원명  | 부서명  |
+--------+--------+
| 홍길동  | 개발팀  |
| 이영희  | 개발팀  |
| 김철수  | 기획팀  |
| 박민수  | 디자인팀 |
| NULL   | 인사팀  |
+--------+--------+

인사팀에는 소속 직원이 없으므로 직원명이 NULL입니다.


CROSS JOIN

모든 행의 조합을 반환합니다. 데이터가 많으면 결과가 폭발적으로 증가하므로 주의합니다.

SELECT e.name, d.name
FROM employees e
CROSS JOIN departments d;
-- 5명 × 4부서 = 20행

SELF JOIN

같은 테이블을 자기 자신과 결합합니다. 계층 구조(상사-부하) 표현에 자주 사용됩니다.

-- 매니저 테이블 예시
CREATE TABLE staff (
    id          INT PRIMARY KEY AUTO_INCREMENT,
    name        VARCHAR(50) NOT NULL,
    manager_id  INT,
    FOREIGN KEY (manager_id) REFERENCES staff(id)
);

INSERT INTO staff (name, manager_id) VALUES
('김대표', NULL),
('이부장', 1),
('박과장', 2),
('최사원', 3);

-- 직원과 상사 이름 함께 조회
SELECT s.name AS 직원, m.name AS 상사
FROM staff s
LEFT JOIN staff m ON s.manager_id = m.id;
+--------+--------+
| 직원    | 상사    |
+--------+--------+
| 김대표  | NULL   |
| 이부장  | 김대표  |
| 박과장  | 이부장  |
| 최사원  | 박과장  |
+--------+--------+

다중 테이블 JOIN

CREATE TABLE projects (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    name     VARCHAR(100) NOT NULL,
    dept_id  INT,
    FOREIGN KEY (dept_id) REFERENCES departments(id)
);

-- 직원 + 부서 + 프로젝트 3개 테이블 결합
SELECT e.name AS 직원명, d.name AS 부서명, p.name AS 프로젝트명
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id
INNER JOIN projects p ON d.id = p.dept_id;

JOIN 사용 시 주의사항

  1. JOIN 조건(ON)을 빠뜨리면 CROSS JOIN이 되어 데이터가 폭발합니다
  2. 컬럼명이 겹칠 때는 반드시 테이블 별칭을 사용합니다 (e.name, d.name)
  3. 인덱스가 없는 컬럼으로 JOIN하면 성능이 크게 저하됩니다
  4. LEFT JOIN 후 WHERE에서 오른쪽 테이블 조건을 걸면 INNER JOIN과 같아질 수 있습니다
-- ❌ LEFT JOIN 의미가 사라짐
SELECT * FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.name = '개발팀';

-- ✅ ON 절에 조건 추가
SELECT * FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id AND d.name = '개발팀';

관련된 글 (mysql > lecture-mysql)