1. JOIN을 하기 전에 먼저 생각해야 할 것
JOIN 문법을 작성하기 전에 반드시 정리해야 할 질문이 있다.
- 각 테이블에서 한 행은 무엇을 의미하는가
- PK와 FK는 무엇인가
- 두 테이블은 어떤 관계인가 (1:1, 1:N)
- 결과 행 수가 늘어나는 것이 의도한 것인가
이 질문에 답하지 않고 JOIN을 하면 쿼리는 실행되지만 결과가 잘못 나오는 경우가 매우 많다.
2. INNER JOIN — 공통으로 존재하는 데이터만 연결하기

개념 정리
INNER JOIN은 두 테이블의 조건 (ON)에 매칭되는 데이터만 남긴다.
→ 즉, 두 테이블에 조인 조건에 의해 공통된 것만 남는다. (교집합으로 이해하면 편하다.)
⭐어디까지 보는게 편하단 의미지 실제로 교집합은 아니다. PK/FK가 같은 행만 연결하는 관계 매칭이다
정확한 표현은 "ON 조건을 만족하는 행 쌍만 결과로 남는다"가 맞을것이다.
언제 사용하나?
“양쪽에 모두 존재하는 데이터만 보고 싶을 때”
- 실제로 수강신청이 있는 학생만 보고 싶을 때
- 주문 이력이 있는 고객만 보고 싶을 때
- 로그가 존재하는 유저만 분석 대상로 삼을 때
둘 다 있는 애들만 → INNER JOIN
예제 상황
- users: 사용자 정보 테이블
- orders: 주문 정보 테이블
각 주문은 반드시 한 명의 사용자와 연결되어 있다.
수도코드
| users 테이블과 orders 테이블을 연결한다 user_id가 같은 행만 남긴다 사용자 이름과 주문 금액을 출력한다 |
실제 SQL 코드
SELECT u.user_id, u.name, o.order_id, o.total_amount
FROM users u
INNER JOIN orders o
ON u.user_id = o.user_id;
Line by Line (실행 흐름)
| 1. FROM users 테이블을 기준으로 읽는다 2. JOIN orders 테이블을 ON 조건으로 연결한다 3. user_id가 일치하는 행만 남긴다 4. SELECT에 지정한 컬럼만 출력한다 |
INNER JOIN에서는 JOIN 조건을 만족하지 않는 행은 처음부터 제거된다.
3. LEFT JOIN — 기준 테이블은 모두 살린다

개념 정리
LEFT JOIN은 왼쪽 테이블의 행은 모두 유지하고, 오른쪽 테이블은 조건이 맞는 경우에만 붙인다.
매칭되지 않는 경우, 오른쪽 컬럼은 NULL이 된다.
→ “기준 테이블 + 참고 정보” 구조로 이해하는 것이 핵심이다.
언제 사용하나?
기준(모수)을 유지하고 싶을 때
- 전체 학생 중 수강신청 여부 확인
- 전체 고객 대비 주문 유무 분석
- 전체 상품 대비 판매 여부 확인
기준만 보고 싶다 → LEFT JOIN
수도코드
| users 테이블을 기준으로 삼는다 orders 테이블을 user_id로 연결한다 주문이 없는 사용자도 결과에 포함한다 |
실제 SQL 코드
SELECT u.user_id, u.name, o.order_id
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id;
Line by Line (실행 흐름)
| 1. FROM users 테이블을 모두 읽는다 2. orders 테이블을 ON 조건으로 연결한다 3. 매칭되지 않는 경우 orders 컬럼은 NULL로 채운다 4. SELECT 컬럼을 출력한다. |
⭐ LEFT JOIN의 핵심은 기준이 되는 테이블이 무엇인지 명확히 인식하는 것이다. ⭐
4. ON vs WHERE — LEFT JOIN에서 자주 발생하는 실수
LEFT JOIN을 사용했는데 조건을 잘못 작성해 결과가 INNER JOIN처럼 나오는 경우가 있다.
그 이유는 대부분 WHERE 조건 때문이다.
4.1) 예시로 확인하기
먼저 기억해야 할것은 ON 절은 JOIN 자체의 조건이다. 어떤 행을 서로 연결할 것인가? 오른쪽 테이블을 붙일지 말지를 결정정한다.
FROM A
LEFT JOIN B
ON A.id = B.id
AND B.status = 'DONE'
이 코드의 의미는
A는 유지
B는...
• B의 id가 A의 id와 같고
• status가 'DONE'인 경우
만약 조건에 맞지 않으면 B 컬럼은 NULL
FROM A
LEFT JOIN B
ON A.id = B.id
WHERE B.status = 'DONE'
JOIN은 일단 수행됨
그 뒤 B.status = 'DONE'인 행만 남김
INNER 조인을 쓴것과 차이를 보이지 않음
LEFT JOIN 결과에서 오른쪽 테이블이 매칭되지 않은 행은
B.status = NULL 이런 행일것이다. 이 상태에서
WHERE B.status = 'DONE' 를 사용하면?
NULL = 'DONE' → FALSE , 결국 해당 행은 제거됨
결과적으로 LEFT JOIN을 했지만, 결과는 INNER JOIN처럼 동작하게 된다.
이것만 기억하자.
ON은 “어떤 행끼리 붙일지” 정할 때 쓴다. (JOIN중 작동)
WHERE는 “붙인 결과 중 무엇을 보여줄지” 정할 때 쓴다. (JOIN 후 작동)
LEFT JOIN에서 오른쪽 테이블 조건은 ON에 쓰는 게 정석이다.
잘못된 예제
SELECT u.user_id, o.order_id
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id
WHERE o.status = 'completed';
이 경우, orders가 NULL인 행은 WHERE에서 제거된다.
결과적으로 LEFT JOIN의 의미가 사라진다.
올바른 방식
SELECT u.user_id, o.order_id
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id
AND o.status = 'completed';
조건이 JOIN 대상에 대한 것이라면 WHERE가 아니라 ON에 작성해야 한다.
5. 다중 JOIN — 테이블이 3개 이상일 때
JOIN은 두 개만 할 수 있는 것이 아니다.
필요하다면 여러 테이블을 연속으로 연결할 수 있다.
- A JOIN B → 결과 AB
- (AB) JOIN C → 결과 ABC
중요한 건 “몇 개를 JOIN하느냐”가 아닌 JOIN을 한 번 할 때마다 결과가 어떻게 변하는지 설명할 수 있느냐다.
수도코드
| users와 orders를 연결한다 orders와 payments를 연결한다 사용자별 주문과 결제 정보를 출력한다 |
실제 SQL 코드
SELECT u.user_id, o.order_id, p.payment_amount
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id
LEFT JOIN payments p
ON o.order_id = p.order_id;
실행 흐름
| 1. users 기준으로 orders를 LEFT JOIN 2. 그 결과를 기준으로 payments를 LEFT JOIN 3. SELECT 컬럼 출력 |
JOIN은 항상 앞 단계의 결과를 기반으로 다음 JOIN이 실행된다.
6. JOIN 결과 검증 — 반드시 확인해야 할 것
JOIN 결과를 확인할 때는 다음을 점검한다.
- JOIN 전후 행 수가 왜 변했는가
- 중복 행이 발생할 가능성은 없는가
- 1:N 관계에서 집계가 필요한 상황은 아닌가
단순히 쿼리가 실행된다고 끝이 아니다.
결과가 의도한 데이터인지 검증하는 과정이 반드시 필요하다.
7. UNION vs JOIN — 언제 무엇을 써야 할까
JOIN
- 테이블을 옆으로 붙인다
- 컬럼 수가 늘어난다
- 관계가 있는 데이터 연결에 사용
UNION
- 결과를 위아래로 합친다
- 컬럼 구조가 동일해야 한다
- 서로 다른 조건의 결과를 합칠 때 사용
JOIN과 UNION은 목적이 완전히 다르다.
대체 관계가 아니라, 사용 상황이 다르다.
오늘 학습한 내용 정리
이번 챕터에서는 JOIN을 실전 관점에서 복습했다.
- JOIN 전에 테이블의 의미와 관계를 먼저 정리해야 한다
- 가장 많이 쓰는 JOIN은 INNER JOIN(교집합)과 LEFT JOIN(전체 테이블)이다
- LEFT JOIN에서는 WHERE 조건 위치를 항상 주의해야 한다
- 다중 JOIN은 순서와 기준 테이블이 중요하다
- JOIN 결과는 반드시 행 수와 중복 여부를 검증해야 한다
- JOIN과 UNION은 목적이 다르다
'F.SQL > SQL 기초 복습' 카테고리의 다른 글
| [SQL] 윈도우 함수 — 분석가의 필수요소 (0) | 2026.01.12 |
|---|---|
| [SQL] 복잡한 쿼리를 다루는 방법 — 서브쿼리와 CTE (0) | 2026.01.04 |
| [SQL] 테이블 구조 이해 & JOIN 준비 운동 (0) | 2026.01.04 |
| [SQL] 데이터를 숫자로 요약하기 — 집계와 그룹화 (0) | 2026.01.03 |
| [SQL] 조건을 조금 더 똑똑하게 — CASE로 데이터 분류하기 (0) | 2026.01.03 |
