F-LAB JAVA · 7주차 · Phase 1 · SQL JOIN: 관계형 DB의 본질
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
INNER JOIN 은 두 테이블에서 ON 조건을 만족하는 (매칭되는) 행만 반환하는 교집합 연산으로, 매칭 안 되는 NULL 이나 한쪽에만 있는 행은 결과에서 제외되며 카르테시안 곱과 달리 조건이 명시되어 효율적이고 ON 절과 WHERE 절은 INNER JOIN 에선 결과가 같지만 OUTER JOIN 에선 다르게 동작한다.
INNER JOIN 은 두 테이블에서 공통된 값이 있는 행만 반환하는 JOIN 의 가장 기본 형태다.
문법은FROM A INNER JOIN B ON A.x = B.y— ON 절의 조건을 만족하는 행만 결과에 포함된다.
매칭이 없는 행 (한쪽 NULL 또는 다른 테이블에 짝 없음) 은 결과에서 제외 된다 (예: Charlie 의 dept_id 가 NULL 이면 제외, Sales 부서에 직원이 없으면 제외).
INNER JOIN 은 의도가 명확한 교집합 연산으로 카르테시안 곱 (모든 조합) 보다 효율적이며 — ON 조건과 WHERE 조건은 INNER JOIN 에선 같은 결과지만 OUTER JOIN 에선 동작이 다르므로 ON 절에 조인 조건을 두는 것이 명확하다.
INNER JOIN = 부부 사진:
상황:
- 남편 명단 (employees)
- 아내 명단 (departments — 부서)
- 매칭 (dept_id) 으로 짝
INNER JOIN (부부만):
- 양쪽 모두 있는 짝만 사진
- 매칭 X 인 사람 제외
- 결혼 안 한 남편 (Charlie) 제외
- 결혼 안 한 아내 (Sales) 제외
결과:
- Alice + HR (1번 짝)
- Bob + Engineering (2번 짝)
- Charlie ❌ (dept_id NULL)
- Sales ❌ (직원 없음)
ON 조건:
- 짝의 조건 (dept_id = id)
- 매칭 기준
vs 카르테시안 (모든 조합):
- 남편 × 아내 모든 쌍
- INNER JOIN 은 조건 만족 짝만
ILIC:
- shipments × customers
- customer_id 매칭 짝
- 고객 없는 배송 X
- 배송 없는 고객 X
→ INNER JOIN = 양쪽 매칭 행만 (교집합), ON 조건이 매칭 기준.
1. INNER JOIN 정의
2. 교집합의 의미
3. INNER JOIN 문법
4. 매칭 없는 행 제외
5. INNER JOIN vs 카르테시안
6. ON vs WHERE 차이
7. 여러 테이블 JOIN
8. 셀프 JOIN
9. 면접 + 자기 점검
INNER JOIN:
두 테이블에서:
- ON 조건 만족 행만
- 양쪽 매칭만 반환
- 교집합
→ JOIN 의 기본
키워드:
- "INNER" = 안쪽 (교집합)
- 기본값 (생략 가능)
- "JOIN" 만 써도 INNER
- 가장 자주 사용
결과 특성:
- 양쪽 매칭만
- 매칭 X 행 제외
- NULL 제외
- 결과 행 수 ≤ min(A, B)
-- INNER JOIN (ILIC)
-- 배송과 그 고객 정보 (둘 다 있는 경우만)
SELECT s.bl_no, c.name AS customer_name
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- 결과:
-- - 고객이 있는 배송만
-- - 배송 있는 고객만
-- - customer_id 가 NULL 인 배송은 제외
-- - 배송 없는 고객은 제외
-- → 가장 자주 쓰는 JOIN
INNER JOIN 의 정의는?
답:
1. 정의:
키워드:
결과:
사용:
교집합 시각화:
┌──── employees ────┐ ┌──── departments ────┐
│ │ │ │
│ Alice (dept=101) │ │ HR (id=101) │
│ Bob (dept=102) ── │──│ Engineering (id=102) │
│ │ │ │
│ Charlie (NULL) ❌│ │ Sales (id=103) ❌ │
│ │ │ │
└────────────────────┘ └───────────────────────┘
↓
INNER JOIN 결과:
┌──────────────────┐
│ Alice + HR │
│ Bob + Engineering │
└──────────────────┘
양쪽 매칭:
Alice (dept_id=101) ↔ HR (id=101) ✅
Bob (dept_id=102) ↔ Engineering (id=102) ✅
Charlie (dept_id=NULL) ↔ ??? ❌ 매칭 X
??? ↔ Sales (id=103) ❌ 매칭 X
→ 2 행 결과
행 수 계산:
- 양쪽 매칭 행만
- 일반적으로:
- 최대 = min(A 행, B 행)
- 1:N 이면 = N 측 행 수
- 1:1 이면 = 매칭 수
데이터 의미:
"유효한 짝":
- 양쪽이 다 갖춘 정보
- 불완전 데이터 제외
- 정상 케이스만
→ 비즈니스 의미 명확
-- 교집합 예시 (ILIC)
-- 고객이 등록된 배송만
SELECT s.id, s.bl_no, c.name, c.email
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- 결과:
-- - 정상적으로 고객 연결된 배송
-- - customer_id 가 NULL 인 (임시) 배송 제외
-- - 사용된 적 없는 고객 제외
-- 운임이 책정된 배송만
SELECT s.id, f.amount
FROM shipments s
INNER JOIN freights f ON f.shipment_id = s.id;
-- 운임 미책정 배송은 제외 (조건 만족 X)
교집합 의미와 결과 행은?
답:
1. 벤다이어그램:
양쪽 매칭:
행 수:
의미:
-- 표준 문법
SELECT 컬럼들
FROM A
INNER JOIN B ON A.x = B.y;
-- INNER 생략 가능
SELECT 컬럼들
FROM A
JOIN B ON A.x = B.y;
-- INNER JOIN 예시
SELECT
e.id,
e.name,
d.dept_name
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id;
-- USING (컬럼명 같을 때)
SELECT *
FROM employees e
INNER JOIN departments d USING (dept_id);
-- e.dept_id = d.dept_id 자동
-- ON 명시 (권장)
SELECT *
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
-- 테이블 별칭 (e, d)
SELECT e.name, d.dept_name
FROM employees e -- e 로 별칭
INNER JOIN departments d -- d 로 별칭
ON e.dept_id = d.id;
-- 권장 이유:
-- - 가독성 ↑
-- - 컬럼 모호성 X
-- - 코드 짧음
-- INNER JOIN 문법 (ILIC)
-- 1. 기본 문법
SELECT s.bl_no, c.name
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- 2. INNER 생략
SELECT s.bl_no, c.name
FROM shipments s
JOIN customers c ON s.customer_id = c.id;
-- 3. 여러 조건 (AND)
SELECT s.bl_no, c.name
FROM shipments s
INNER JOIN customers c
ON s.customer_id = c.id
AND c.active = TRUE;
-- → 활성 고객만
-- 4. 컬럼 명시 (가독성)
SELECT
s.id AS shipment_id,
s.bl_no,
c.name AS customer_name,
c.email AS customer_email
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
INNER JOIN 문법은?
답:
1. 문법:
INNER 생략:
USING:
별칭:
NULL 인 경우:
Charlie 의 dept_id = NULL
ON 조건: e.dept_id = d.id
→ NULL = ??? 매칭 X
→ Charlie 제외
(NULL 비교는 항상 NULL, 거짓 취급)
짝 없는 경우:
Sales 부서 (id=103)
→ 어떤 직원도 dept_id=103 없음
→ Sales 도 결과 X
SQL NULL 동작:
NULL = NULL → NULL (참 X)
NULL = 101 → NULL
NULL <> 101 → NULL
WHERE 절: NULL 은 거짓 취급
→ 결과에서 제외
-- NULL 비교는 IS NULL 사용
WHERE x IS NULL -- 참
WHERE x = NULL -- 항상 NULL (거짓)
-- 부서 없는 직원 (LEFT JOIN 으로)
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.id IS NULL;
-- → INNER JOIN 으론 못 찾음
-- 매칭 없는 행 제외 (ILIC)
-- 예: 고객 미등록 임시 배송 + 미사용 고객
INSERT INTO shipments VALUES (10, 'BL010', NULL); -- 고객 NULL
INSERT INTO customers VALUES (5, 'X 무역'); -- 사용 X
-- INNER JOIN
SELECT s.bl_no, c.name
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- 결과:
-- - id=10 배송 ❌ (customer_id NULL)
-- - id=5 고객 ❌ (배송 연결 X)
-- → 정상 매칭만 (양쪽 다 있는 경우)
-- "고객 없는 배송 찾기" 는 LEFT JOIN 으로 (다음 Unit)
NULL / 매칭 없음 시 제외되는 이유는?
답:
1. NULL:
짝 없음:
NULL 동작:
찾기:
카르테시안 곱 (Cartesian Product):
두 테이블의 모든 조합:
- A 의 모든 행 × B 의 모든 행
- A=3, B=3 → 9 행
- 의미 없는 조합 다수
-- 카르테시안 곱 (의도치 않은 경우 많음)
SELECT *
FROM employees e, departments d;
-- 또는
SELECT *
FROM employees e CROSS JOIN departments d;
-- 결과: 3 × 3 = 9 행 (모든 조합)
INNER JOIN 과 차이:
카르테시안:
- 모든 조합 (의미 X 다수)
- A × B 행
- 매우 큼
INNER JOIN:
- 조건 만족 조합만
- 의미 있는 행
- ≤ min(A, B) 일반적
의도치 않은 카르테시안:
SELECT *
FROM employees e, departments d;
-- 조인 조건 없음 → 카르테시안
SELECT *
FROM employees e, departments d
WHERE e.dept_id = d.id;
-- WHERE 로 조건 → 사실상 INNER JOIN
-- (옛 SQL 스타일)
→ 명시적 INNER JOIN 권장
-- 카르테시안 사고 (ILIC)
-- ❌ 위험: JOIN 조건 빠뜨림
SELECT s.bl_no, c.name
FROM shipments s, customers c;
-- → 모든 배송 × 모든 고객
-- → shipments 1000 × customers 500 = 500,000 행!
-- → 의미 없음
-- ✓ 안전: 명시적 INNER JOIN
SELECT s.bl_no, c.name
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- → 정상 매칭만 (보통 = shipments 행 수)
-- 옛 스타일 (지양):
SELECT s.bl_no, c.name
FROM shipments s, customers c
WHERE s.customer_id = c.id;
-- 같은 결과지만 명시적 JOIN 권장
INNER JOIN vs 카르테시안 차이는?
답:
1. 카르테시안:
INNER JOIN:
위험:
권장:
-- INNER JOIN: 결과 같음
-- 방법 1: ON 절
SELECT *
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id
WHERE d.dept_name = 'HR';
-- 방법 2: WHERE 절
SELECT *
FROM employees e
INNER JOIN departments d
ON 1=1
WHERE e.dept_id = d.id
AND d.dept_name = 'HR';
-- → INNER JOIN 에선 같은 결과
-- LEFT JOIN: 차이 발생!
-- ON 에 부서명 조건
SELECT *
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.id AND d.dept_name = 'HR';
-- → 모든 직원 + HR 만 매칭
-- WHERE 에 부서명 조건
SELECT *
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.id
WHERE d.dept_name = 'HR';
-- → HR 부서 직원만 (다른 부서 + NULL 제외)
-- → 사실상 INNER JOIN
-- → OUTER JOIN 에선 ON 과 WHERE 다름!
차이 이유:
ON 절:
- JOIN 시점 적용
- LEFT 의 모든 행 유지
- 조건 안 맞아도 LEFT 행은 남음
WHERE 절:
- JOIN 후 필터
- NULL 행도 필터 대상
- 조건 안 맞으면 제거
권장 사용:
ON 절:
- 조인 조건 (테이블 연결)
- JOIN 의 본질
WHERE 절:
- 결과 필터링
- 의미 명확
→ 역할 분리
-- ON vs WHERE 차이 (ILIC)
-- 시나리오: 활성 고객의 배송만
-- INNER JOIN: 같은 결과
-- 방법 1
SELECT s.*, c.name
FROM shipments s
INNER JOIN customers c
ON s.customer_id = c.id AND c.active = TRUE;
-- 방법 2
SELECT s.*, c.name
FROM shipments s
INNER JOIN customers c
ON s.customer_id = c.id
WHERE c.active = TRUE;
-- → 같은 결과
-- LEFT JOIN: 다른 결과 (다음 Unit 에서 자세히)
SELECT s.*, c.name
FROM shipments s
LEFT JOIN customers c
ON s.customer_id = c.id AND c.active = TRUE;
-- → 모든 배송 + 활성 고객만 (비활성 고객은 NULL)
SELECT s.*, c.name
FROM shipments s
LEFT JOIN customers c
ON s.customer_id = c.id
WHERE c.active = TRUE;
-- → 활성 고객 배송만 (LEFT 효과 사라짐)
-- 권장:
-- - JOIN 조건은 ON
-- - 비즈니스 필터는 WHERE
ON 조건 vs WHERE 조건 차이는?
답:
1. INNER:
OUTER:
이유:
권장:
-- 3개 테이블 JOIN
SELECT
e.name,
d.dept_name,
c.company_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id
INNER JOIN companies c ON d.company_id = c.id;
-- → 직원 → 부서 → 회사
체인:
A INNER JOIN B
ON A.x = B.y
INNER JOIN C
ON B.z = C.w
→ A→B→C 연결
→ 한 결과
성능 (다중 JOIN):
- 인덱스 (FK) 활용 필수
- JOIN 개수 ↑ → 비용 ↑
- DB 옵티마이저가 순서 결정
- 5+ JOIN 은 신중
조인 순서:
옵티마이저가 결정:
- 작은 테이블 먼저
- 선택도 높은 조건 먼저
- 인덱스 활용
EXPLAIN 으로 확인:
EXPLAIN SELECT ...;
-- 여러 테이블 JOIN (ILIC)
-- 배송 상세 (5 테이블)
SELECT
s.bl_no,
c.name AS customer_name,
pl.port_name AS port_loading,
pd.port_name AS port_discharge,
f.amount AS freight_amount
FROM shipments s
INNER JOIN customers c
ON s.customer_id = c.id
INNER JOIN ports pl
ON s.port_loading_id = pl.id
INNER JOIN ports pd
ON s.port_discharge_id = pd.id
INNER JOIN freights f
ON f.shipment_id = s.id
WHERE s.status = 'SHIPPED';
-- 5 테이블 INNER JOIN
-- 모두 매칭되어야 결과
-- 인덱스 (FK) 필수
-- ILIC 일반 패턴:
-- - 핵심 테이블 + 참조 테이블 4-5 JOIN
-- - 정규화된 102 테이블 환경에서 일반
여러 테이블 JOIN 은?
답:
1. 3개 이상:
체인:
성능:
순서:
셀프 JOIN (Self Join):
같은 테이블끼리 JOIN:
- 별칭으로 구분
- 계층 / 관계
-- 직원과 매니저 (셀프 JOIN)
CREATE TABLE employees (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
manager_id BIGINT,
FOREIGN KEY (manager_id) REFERENCES employees(id)
);
-- 직원 + 그의 매니저
SELECT
e.name AS employee,
m.name AS manager
FROM employees e
LEFT JOIN employees m -- 같은 테이블, 다른 별칭!
ON e.manager_id = m.id;
결과:
employee │ manager
─────────┼─────────
Alice │ Carol ← Alice 의 manager
Bob │ Carol
Carol │ NULL ← Carol 은 최상위
...
셀프 JOIN 활용:
- 계층 구조 (매니저-부하)
- 친구 관계
- 답글 (parent_id)
- 카테고리 (parent_category)
-- 셀프 JOIN (ILIC)
-- 배송 관계 (모선 - 환적선)
CREATE TABLE shipments (
id BIGINT PRIMARY KEY,
bl_no VARCHAR(50),
parent_shipment_id BIGINT, -- 모선 ID (환적 시)
FOREIGN KEY (parent_shipment_id) REFERENCES shipments(id)
);
-- 자식 배송 + 모선 정보
SELECT
child.bl_no AS child_bl,
parent.bl_no AS parent_bl
FROM shipments child
LEFT JOIN shipments parent -- 같은 테이블
ON child.parent_shipment_id = parent.id;
-- ILIC 의 자기 참조 패턴 예시:
-- - 배송 환적 (parent-child)
-- - 카테고리 계층
-- - 부서 계층
셀프 JOIN 은?
답:
1. 셀프 JOIN:
별칭:
예시:
활용:
| Q | 핵심 답변 |
|---|---|
| INNER JOIN? | 양쪽 매칭만 |
| 교집합? | 결과 행 |
| 문법? | FROM A JOIN B ON ... |
| 매칭 없음? | 제외 |
| 카르테시안? | 모든 조합 |
| ON vs WHERE? | OUTER 에서 다름 |
| 다중 JOIN? | 체인 |
| 셀프 JOIN? | 같은 테이블 |
| INNER 생략? | 가능 |
| 별칭? | 권장 |
답:
답:
답:
답:
답:
1. INNER JOIN = 교집합
2. 카르테시안과 차이
FROM A, B WHERE 지양)3. ON vs WHERE
이번 Unit에서 INNER JOIN 을 봤다면, 다음은 OUTER JOIN (LEFT/RIGHT).
📚 Phase 1 — SQL JOIN
✅ Unit 1.1 JOIN 이 필요한 이유
✅ Unit 1.2 INNER JOIN ← 여기
⏭ Unit 1.3 LEFT/RIGHT JOIN
⏭ Unit 1.4 FULL OUTER JOIN
⏭ Unit 1.5 JOIN 선택 가이드 ★깊이
🗂️ Part A — 데이터 모델링과 ORM
📚 Phase 1 (2/5)
총: 2/24 Unit