F-LAB JAVA · 7주차 · Phase 1 · SQL JOIN: 관계형 DB의 본질
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
LEFT JOIN 은 왼쪽 테이블의 모든 행을 반환하고 오른쪽 매칭이 없으면 NULL 로 채우며 (Charlie 처럼 부서 없는 직원도 결과에 포함), RIGHT JOIN 은 반대로 오른쪽 모든 행을 반환하고 A LEFT JOIN B = B RIGHT JOIN A 라는 점에서 실무에선 가독성 좋은 LEFT JOIN 만 거의 사용한다.
LEFT JOIN (= LEFT OUTER JOIN) 은 왼쪽 테이블의 모든 행을 보존 하면서 오른쪽과 매칭되는 행을 합치고, 매칭이 없으면 오른쪽 컬럼은 NULL 로 채운다.
그래서 부서가 NULL 인 Charlie 도 LEFT JOIN 결과에는 나타나며 (부서 정보만 NULL), 이것이 INNER JOIN 과 결정적 차이다.
RIGHT JOIN 은 정반대 — 오른쪽 테이블의 모든 행을 보존해 직원 없는 Sales 부서도 결과에 나타나며 (직원 정보만 NULL),A LEFT JOIN B와B RIGHT JOIN A는 같은 결과 를 낸다.
실무에선 LEFT JOIN 이 압도적으로 자주 사용되는데 — 사람의 사고가 "기준 테이블 + 부가 정보" 흐름이라 왼쪽에 기준을 두는 게 자연스럽고, RIGHT JOIN 은 가독성을 해쳐 거의 LEFT JOIN 으로 표현한다.
LEFT JOIN = 학급 전체 명단 + 출석 정보:
LEFT 테이블 (학급 명단):
- 모든 학생 (기준)
- "이 학급의 모두"
RIGHT 테이블 (출석부):
- 오늘 출석한 학생
LEFT JOIN:
- 학급 모든 학생 (기준 유지)
+ 출석 정보 매칭
- 결석한 학생 → 출석 정보 NULL
- "Alice 출석, Bob 출석, Charlie 결석 (NULL)"
INNER JOIN 이면:
- 출석한 학생만
- 결석자 사라짐 ❌
→ "기준 데이터 누락 X"
RIGHT JOIN:
- 출석부 기준
- 명단에 없는 출석은? (있다면) NULL
- 일반적으로 어색
ILIC:
- 모든 배송 (LEFT) + 운임 (있으면)
- 운임 미정 배송도 포함
- INNER 면 운임 있는 배송만
A LEFT JOIN B = B RIGHT JOIN A:
- 좌우만 바꾼 같은 결과
- 가독성 위해 LEFT 만
→ LEFT JOIN = 왼쪽 모두 + 매칭 (NULL 채움), RIGHT 는 반대, 실무는 LEFT 위주.
1. LEFT JOIN 정의
2. OUTER JOIN 의미
3. 매칭 없으면 NULL
4. RIGHT JOIN 정의
5. A LEFT JOIN B = B RIGHT JOIN A
6. LEFT JOIN 이 더 자주 쓰임
7. 부서 없는 직원 찾기
8. LEFT JOIN 실무 활용
9. 면접 + 자기 점검
LEFT JOIN (LEFT OUTER JOIN):
왼쪽 테이블의 모든 행 +
오른쪽 매칭되는 행
(매칭 없으면 NULL)
→ 왼쪽 기준
핵심:
- 왼쪽 모든 행 보존
- 오른쪽 매칭 시 합침
- 매칭 없으면 오른쪽 NULL
→ "왼쪽 행 X" 없음
OUTER:
LEFT JOIN = LEFT OUTER JOIN
- OUTER 생략 가능
- 보통 LEFT JOIN
→ INNER 와 대조 (양쪽 매칭만)
-- LEFT JOIN (ILIC)
-- 모든 배송 + 고객 (있으면)
SELECT
s.id,
s.bl_no,
c.name AS customer_name
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id;
-- 결과:
-- - 모든 배송 (기준)
-- - 고객 연결된 배송: 고객명 채워짐
-- - 고객 미연결 (customer_id NULL): customer_name 도 NULL
-- - "임시 배송"도 모두 포함
-- → 빠뜨림 없이 모든 배송 확인
LEFT JOIN 의 정의는?
답:
1. 정의:
핵심:
OUTER:
목적:
OUTER:
"바깥쪽도 포함":
- 매칭 안 되는 행도
- 한쪽이라도 있으면
→ INNER (안쪽만) 의 반대
3가지 OUTER:
LEFT OUTER JOIN:
- 왼쪽 모두 보존
RIGHT OUTER JOIN:
- 오른쪽 모두 보존
FULL OUTER JOIN:
- 양쪽 모두 보존 (다음 Unit)
INNER vs OUTER:
INNER:
- 양쪽 매칭만
- 한쪽 NULL 제외
OUTER (LEFT):
- 왼쪽 전부
- 오른쪽 매칭 없어도 NULL
→ "보존 vs 제외"
시각화:
INNER JOIN:
┌────A────┐ ┌────B────┐
│ │ │ │
│ ▓▓▓│▓│▓▓▓ │
│ │ │ │
└─────────┘ └─────────┘
교집합만
LEFT JOIN:
┌────A────┐ ┌────B────┐
│▓▓▓▓▓▓▓▓▓│▓│▓▓▓ │
│▓▓▓▓▓▓▓▓▓│ │ │
└─────────┘ └─────────┘
A 전체 + 매칭
-- INNER vs LEFT JOIN (ILIC)
-- INNER JOIN: 고객 매칭된 배송만
SELECT s.bl_no, c.name
FROM shipments s
INNER JOIN customers c ON s.customer_id = c.id;
-- 결과: 5 행 (고객 없는 배송 제외)
-- LEFT JOIN: 모든 배송 + 고객 (있으면)
SELECT s.bl_no, c.name
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id;
-- 결과: 7 행 (임시 배송 2개 포함, c.name=NULL)
-- → LEFT JOIN 으로 누락 X
OUTER JOIN 의 의미는?
답:
1. OUTER:
3가지:
vs INNER:
시각화:
NULL 채움:
LEFT JOIN 에서:
- 왼쪽 행 존재
- 오른쪽 매칭 X
→ 오른쪽 컬럼 모두 NULL
결과 행에 NULL 포함
employees:
┌────┬─────────┬─────────┐
│ id │ name │ dept_id │
├────┼─────────┼─────────┤
│ 1 │ Alice │ 101 │
│ 2 │ Bob │ 102 │
│ 3 │ Charlie │ NULL │
└────┴─────────┴─────────┘
departments:
┌─────┬──────────────┐
│ id │ dept_name │
├─────┼──────────────┤
│ 101 │ HR │
│ 102 │ Engineering │
│ 103 │ Sales │
└─────┴──────────────┘
SELECT e.id, e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
결과:
┌────┬─────────┬──────────────┐
│ id │ name │ dept_name │
├────┼─────────┼──────────────┤
│ 1 │ Alice │ HR │
│ 2 │ Bob │ Engineering │
│ 3 │ Charlie │ NULL │ ← 매칭 없음
└────┴─────────┴──────────────┘
Charlie 가 살아남는 이유:
LEFT JOIN:
- employees 모든 행 보존 (기준)
- Charlie dept_id=NULL
- departments 매칭 X
→ dept_name = NULL 채움
→ 행 자체는 결과에
INNER 였다면:
→ Charlie 제외 (이전 Unit)
-- 매칭 없으면 NULL (ILIC)
-- 시나리오:
-- shipments 5건, 그 중 1건은 customer_id=NULL (임시)
-- customers 10명, 그 중 일부 사용 X
-- LEFT JOIN
SELECT
s.id,
s.bl_no,
c.name AS customer_name,
c.email AS customer_email
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id;
-- 결과: 5 행
-- - 4 행: 고객 연결 (정상)
-- - 1 행: customer_name=NULL, customer_email=NULL
-- (임시 배송)
-- → 모든 배송 누락 X
-- → 고객 없는 케이스 식별 가능
매칭 없으면 NULL 의 동작은?
답:
1. NULL 채움:
Charlie:
이유:
활용:
RIGHT JOIN (RIGHT OUTER JOIN):
오른쪽 테이블의 모든 행 +
왼쪽 매칭되는 행
(매칭 없으면 NULL)
→ LEFT 의 반대
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;
결과:
┌─────────┬──────────────┐
│ name │ dept_name │
├─────────┼──────────────┤
│ Alice │ HR │
│ Bob │ Engineering │
│ NULL │ Sales │ ← 직원 없는 부서
└─────────┴──────────────┘
Sales 가 살아남는 이유:
RIGHT JOIN:
- departments 모든 행 보존 (기준)
- Sales (id=103) 매칭 직원 X
→ name = NULL 채움
→ 행 자체는 결과에
→ 직원 없는 부서도 식별
RIGHT JOIN:
┌────A────┐ ┌────B────┐
│ ▓▓▓│▓│▓▓▓▓▓▓▓▓▓│
│ │ │▓▓▓▓▓▓▓▓▓│
└─────────┘ └─────────┘
매칭 + B 전체
-- RIGHT JOIN (ILIC)
-- 모든 고객 + 그들의 배송 (있으면)
SELECT
s.bl_no,
c.name AS customer_name
FROM shipments s
RIGHT JOIN customers c ON s.customer_id = c.id;
-- 결과:
-- - 배송 있는 고객: 정상 매칭
-- - 배송 없는 고객: bl_no=NULL
-- → 모든 고객 확인 (배송 유무 식별)
-- 사용 X 고객 찾기:
SELECT c.name
FROM shipments s
RIGHT JOIN customers c ON s.customer_id = c.id
WHERE s.id IS NULL;
-- → 배송 한 번도 없는 고객
RIGHT JOIN 의 정의는?
답:
1. 정의:
예시:
이유:
활용:
동치 관계:
A LEFT JOIN B
=
B RIGHT JOIN A
→ 같은 결과
→ 좌우만 바꿈
-- LEFT JOIN
SELECT *
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
-- 동치 RIGHT JOIN
SELECT *
FROM departments d
RIGHT JOIN employees e ON e.dept_id = d.id;
-- → 같은 결과
-- (FROM 절 순서만 바뀜)
다른 점 (사소함):
컬럼 순서:
- SELECT * 의 결과
- LEFT: A 컬럼 먼저, B 컬럼 뒤
- RIGHT: B 먼저, A 뒤
→ 의미는 같음
변환 가능:
RIGHT JOIN 코드 → LEFT JOIN:
1. FROM 절 순서 바꿈
2. RIGHT → LEFT
→ 모든 RIGHT 는 LEFT 로 표현 가능
-- 동치 변환 (ILIC)
-- RIGHT JOIN 사용 (덜 흔함)
SELECT s.bl_no, c.name
FROM shipments s
RIGHT JOIN customers c ON s.customer_id = c.id;
-- 동치 LEFT JOIN (권장 스타일)
SELECT s.bl_no, c.name
FROM customers c
LEFT JOIN shipments s ON s.customer_id = c.id;
-- → 같은 결과
-- → LEFT 가 더 자연스러움
-- → ILIC 표준은 LEFT JOIN
A LEFT JOIN B = B RIGHT JOIN A 인가?
답:
1. 동치:
변환:
차이:
권장:
통계적 사실:
실무 SQL 의 OUTER JOIN:
- LEFT JOIN: ~95%
- RIGHT JOIN: ~5%
→ LEFT 압도적
사고 흐름:
사람의 자연 사고:
- "이 테이블 + 부가 정보"
- 기준이 왼쪽
예:
- "모든 직원 + 부서 정보"
- "모든 주문 + 상품 정보"
→ LEFT 가 자연
가독성:
LEFT JOIN:
- 왼쪽 = 기준 (FROM 절)
- 오른쪽 = 추가 정보
- 직관적
RIGHT JOIN:
- 왼쪽 = FROM 절이지만 기준은 오른쪽
- 헷갈림
→ LEFT 가 명확
다중 JOIN:
A LEFT JOIN B LEFT JOIN C LEFT JOIN D
- 일관된 흐름
- 왼쪽 = 항상 기준
RIGHT JOIN 섞이면:
- 어디가 기준?
- 가독성 ↓
실무 권장:
- LEFT JOIN 만 사용
- RIGHT JOIN 은 LEFT 로 변환
- 일관성 유지
→ 코드 리뷰에서도 표준
-- ILIC 의 일관된 LEFT JOIN 패턴
-- 5 테이블 LEFT JOIN (일관)
SELECT
s.bl_no,
c.name,
p.port_name,
f.amount,
si.item_name
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id
LEFT JOIN ports p ON s.port_loading_id = p.id
LEFT JOIN freights f ON f.shipment_id = s.id
LEFT JOIN shipment_items si ON si.shipment_id = s.id;
-- 흐름:
-- - shipments (기준)
-- - 각 LEFT JOIN 으로 부가 정보 추가
-- - 부가 정보 없어도 배송 행 유지
-- ❌ RIGHT JOIN 섞으면 (안 좋음)
SELECT *
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id
RIGHT JOIN ports p ON s.port_loading_id = p.id;
-- 기준이 어디인지 헷갈림
LEFT JOIN 이 더 자주 쓰이는 이유는?
답:
1. 통계:
사고 흐름:
가독성:
다중 JOIN:
차집합 (왼쪽 - 매칭):
"왼쪽에는 있지만 오른쪽에 없는 행":
- LEFT JOIN 의 활용
- + WHERE IS NULL
-- 부서 없는 직원
SELECT e.id, e.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.id IS NULL;
-- → Charlie 만 결과
동작 원리:
1. LEFT JOIN 결과:
- Alice + HR
- Bob + Engineering
- Charlie + NULL ← 매칭 없음
2. WHERE d.id IS NULL:
- Charlie 만 통과
- 정상 매칭은 d.id 가 있으므로 제외
→ 차집합 추출
-- 직원 없는 부서 (반대 차집합)
SELECT d.id, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id
WHERE e.id IS NULL;
-- 또는 (LEFT 로 표현, 권장)
SELECT d.id, d.dept_name
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id
WHERE e.id IS NULL;
-- → Sales 만 결과
활용 사례:
- 미사용 코드 찾기
- 고아 데이터 (orphan)
- 미할당 자원
- 누락 데이터 감지
-- 차집합 활용 (ILIC)
-- 1. 고객 없는 배송 (임시 배송, 정리 대상?)
SELECT s.id, s.bl_no
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id
WHERE c.id IS NULL;
-- 2. 배송 한 번도 없는 고객 (마케팅 대상?)
SELECT c.id, c.name
FROM customers c
LEFT JOIN shipments s ON s.customer_id = c.id
WHERE s.id IS NULL;
-- 3. 운임 미책정 배송 (확인 필요)
SELECT s.id, s.bl_no
FROM shipments s
LEFT JOIN freights f ON f.shipment_id = s.id
WHERE f.id IS NULL;
-- 4. 사용 안 된 항구 (정리?)
SELECT p.id, p.port_name
FROM ports p
LEFT JOIN shipments s ON s.port_loading_id = p.id
WHERE s.id IS NULL;
-- → 운영 관리에 매우 유용
부서 없는 직원 찾기 쿼리는?
답:
1. 방법:
원리:
반대:
활용:
실무 패턴:
1. 모든 X 와 부가 정보:
- "모든 주문 + 배송 정보"
2. 부가 정보 누락 허용:
- 운임 미정 배송도 보여줌
3. 차집합:
- WHERE IS NULL
-- NULL 을 기본값으로
SELECT
s.bl_no,
COALESCE(c.name, '미등록') AS customer_name,
COALESCE(f.amount, 0) AS freight_amount
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id
LEFT JOIN freights f ON f.shipment_id = s.id;
-- COALESCE: NULL 이면 다른 값
-- IFNULL (MySQL) / NVL (Oracle) 도 같은 기능
-- 모든 부서별 직원 수 (직원 없어도 부서 표시)
SELECT
d.dept_name,
COUNT(e.id) AS employee_count
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id
GROUP BY d.id, d.dept_name;
-- 결과:
-- HR | 1
-- Engineering | 1
-- Sales | 0 ← 직원 없는 부서도 표시
-- INNER JOIN 이면 Sales 누락
-- 1:N 에서 LEFT JOIN
-- 모든 고객 + 그들의 배송 (있으면, 여러 개)
SELECT
c.name,
s.bl_no
FROM customers c
LEFT JOIN shipments s ON s.customer_id = c.id;
-- 고객 1명에 배송 N개:
-- - 행 N 개 (배송별로)
-- 고객 1명에 배송 0개:
-- - 1 행 + bl_no=NULL
-- LEFT JOIN 종합 (ILIC)
-- 1. 대시보드 (모든 활성 배송 + 정보)
SELECT
s.id,
s.bl_no,
s.status,
COALESCE(c.name, '미등록') AS customer,
COALESCE(f.amount, 0) AS freight,
COUNT(si.id) AS item_count
FROM shipments s
LEFT JOIN customers c ON s.customer_id = c.id
LEFT JOIN freights f ON f.shipment_id = s.id
LEFT JOIN shipment_items si ON si.shipment_id = s.id
WHERE s.status IN ('BOOKED', 'CONFIRMED')
GROUP BY s.id, s.bl_no, s.status, c.name, f.amount;
-- → 정보 누락 없이 모든 활성 배송 표시
-- → ILIC 대시보드의 일반 패턴
LEFT JOIN 의 실무 활용은?
답:
1. 기본 패턴:
NULL 처리:
집계:
종합:
| Q | 핵심 답변 |
|---|---|
| LEFT JOIN? | 왼쪽 모두 + 매칭 |
| RIGHT JOIN? | 오른쪽 모두 |
| OUTER 의미? | 바깥쪽 포함 |
| 매칭 없으면? | NULL |
| Charlie? | LEFT 에 등장 |
| LEFT = RIGHT? | 좌우 바꿈 |
| 왜 LEFT 만? | 가독성/사고 |
| 부서 없는 직원? | LEFT + IS NULL |
| COALESCE? | NULL 처리 |
| 1:N? | 여러 행 |
답:
답:
답:
답:
답:
1. LEFT JOIN = 왼쪽 모두 + 매칭
2. RIGHT JOIN = LEFT 의 반대
A LEFT JOIN B = B RIGHT JOIN A (좌우 바꾼 동치)3. 차집합과 활용
이번 Unit에서 LEFT/RIGHT JOIN 을 봤다면, 다음은 FULL OUTER JOIN (양쪽 모두).
📚 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 (3/5)
총: 3/24 Unit
F-LAB JAVA · 7주차 · Phase 1 · Unit 1.3 · 끝