[MySQL] 04. JOIN (테이블 결합)
여러 테이블을 결합하여 데이터를 조회하는 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 사용 시 주의사항
- JOIN 조건(ON)을 빠뜨리면 CROSS JOIN이 되어 데이터가 폭발합니다
- 컬럼명이 겹칠 때는 반드시 테이블 별칭을 사용합니다 (
e.name,d.name) - 인덱스가 없는 컬럼으로 JOIN하면 성능이 크게 저하됩니다
- 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] 13. 자주 발생하는 Troubleshooting
- [MySQL] 12. 성능 튜닝 (Performance Tuning)
- [MySQL] 11. 백업과 복구 (Backup & Restore)
- [MySQL] 10. 사용자 관리와 권한 (User & Privilege)
- [MySQL] 09. 뷰, 스토어드 프로시저, 함수, 트리거
- [MySQL] 08. 트랜잭션과 락 (Transaction & Lock)
- [MySQL] 07. 인덱스 (Index)
- [MySQL] 06. 내장 함수 정리
- [MySQL] 05. 서브쿼리와 고급 SELECT
- [MySQL] 04. JOIN (테이블 결합)
- [MySQL] 03. CRUD 기본 (INSERT, SELECT, UPDATE, DELETE)
- [MySQL] 02. 데이터베이스와 테이블 관리
- [MySQL] 01. MySQL/MariaDB 소개 및 설치