PostgreSQL 교재 · 9편 / 24편

JOIN 4종 — INNER·LEFT·RIGHT·FULL

관계형 DB 의 진가. 4종 JOIN 그림 비교와 카티시안 곱의 함정.

SQL 기초읽는 시간 8분2026-05-17
INNER LEFT RIGHT FULL JOIN 의 벤 다이어그램이 4개로 비교된 도식

관계형 DB 의 "관계" 가 살아나는 순간이 JOIN 입니다. 두 테이블을 공통 키로 연결해 한 결과로 — 이게 안 되면 매번 N+1 쿼리 지옥. 9편은 4가지 JOIN 의 차이를 그림과 예제로 정확히 보고, 가장 흔한 사고 카티시안 곱 도 정리합니다.

실습 테이블 — 사용자와 주문:

users          orders
─────          ──────
id name        id user_id total
 1 준성         1     1   10000
 2 홍길동       2     1    5000
 3 김자바       3     2   30000
                4    99   99000   ← 고아 데이터(없는 user)

INNER JOIN — 양쪽에 다 있는 것만

SELECT u.name, o.id AS order_id, o.total
FROM   users u
JOIN   orders o ON o.user_id = u.id;

-- 결과 (3 행) — user_id=99 와 김자바(주문 없음)는 빠짐
-- 준성 | 1 | 10000
-- 준성 | 2 |  5000
-- 홍길동| 3 | 30000

JOIN 만 쓰면 INNER JOIN 입니다. 양쪽 테이블 모두 매치되는 행만 결과에 포함.

LEFT JOIN — 왼쪽은 다, 오른쪽은 있으면

SELECT u.name, o.id AS order_id, o.total
FROM   users u
LEFT JOIN orders o ON o.user_id = u.id;

-- 결과 (4 행) — 주문 없는 김자바도 NULL 로 포함
-- 준성  | 1    | 10000
-- 준성  | 2    |  5000
-- 홍길동| 3    | 30000
-- 김자바| NULL |  NULL  ← 주문 없음

-- "주문이 없는 사용자" 찾기 (anti-join)
SELECT u.* FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;

LEFT JOIN + IS NULL = anti-join. "왼쪽에는 있지만 오른쪽에는 없는" 행을 찾는 정통 패턴. NOT EXISTS 도 같은 의미.

RIGHT JOIN — 거의 안 쓴다

SELECT u.name, o.id AS order_id
FROM   users u
RIGHT JOIN orders o ON o.user_id = u.id;

-- 결과 — orders 의 모든 행 + users 매치 (없으면 NULL)
-- 준성   | 1
-- 준성   | 2
-- 홍길동 | 3
-- NULL   | 4    ← user_id=99 는 users 에 없음

RIGHT 는 LEFT 와 거울. 가독성 차원에서 거의 항상 LEFT 로 적는 게 컨벤션 — FROM/JOIN 순서만 바꾸면 됩니다.

FULL OUTER JOIN — 양쪽 다 (없으면 NULL)

SELECT u.name, o.id AS order_id
FROM   users u
FULL OUTER JOIN orders o ON o.user_id = u.id;

-- 결과 — INNER + LEFT 만 + RIGHT 만 합집합
-- 준성   | 1
-- 준성   | 2
-- 홍길동 | 3
-- 김자바 | NULL    ← 주문 없는 사용자
-- NULL   | 4       ← 사용자 없는 주문

데이터 동기화·차이 검사에서 쓸 때 있지만 일상 쿼리에서는 드물게 사용.

4종 한 그림 (텍스트로)

JOIN왼쪽만양쪽 매치오른쪽만
INNER
LEFT✓ (NULL)
RIGHT✓ (NULL)
FULL OUTER✓ (NULL)✓ (NULL)

ON vs USING — 조건 표기

-- ON — 일반적 (다른 컬럼 이름끼리도 가능)
JOIN orders o ON o.user_id = u.id

-- USING — 컬럼 이름이 같을 때만 (Postgres 가 컬럼 합쳐줌)
SELECT u.name, o.id
FROM users u
JOIN orders o USING (user_id);   -- u.user_id 와 o.user_id 가 같은 이름

self-join — 같은 테이블을 자기 자신과

-- 직원-매니저 (manager_id 가 같은 employees.id 참조)
SELECT e.name AS employee, m.name AS manager
FROM   employees e
LEFT JOIN employees m ON m.id = e.manager_id;

계층 구조, 시간차 비교(어제 vs 오늘), 짝짓기 등에서 등장합니다. 별칭이 필수.

카티시안 곱 — 잘못된 JOIN 의 함정

-- ❌ ON 조건 빼먹기
SELECT u.name, o.id FROM users u, orders o;
-- 결과: users.count × orders.count = 3 × 4 = 12 행
--       모든 조합이 다 나옴 ← 사고

-- ❌ 잘못된 조건 (조건이 거의 항상 참)
JOIN orders o ON 1 = 1

-- ✅ 항상 매칭 키 명시
JOIN orders o ON o.user_id = u.id

대량 사고 1번지. 결과 행 수가 예상보다 훨씬 많으면 99% JOIN 조건이 빠지거나 잘못된 것입니다. WHERE 추가 전에 SELECT COUNT(*) 로 행 수부터 확인하는 습관.

여러 테이블 — 3개 이상 JOIN

SELECT u.name, o.id, oi.product_id, p.name AS product
FROM   users u
JOIN   orders o      ON o.user_id = u.id
JOIN   order_items oi ON oi.order_id = o.id
JOIN   products p     ON p.id = oi.product_id
WHERE  u.id = 1;

실무 쿼리는 보통 3-5 개 테이블을 엮습니다. 너무 많아지면(7+ 테이블) 한 번 더 생각 — 보통은 분해해서 CTE 또는 뷰가 더 깔끔합니다(10·13편).

10편 — 서브쿼리와 CTE(WITH)

중첩 SELECT 와 가독성 좋은 WITH 절. 재귀 CTE 한 줄까지.

📚 PostgreSQL 배우기 교재
이전: 8편 GROUP BY · 현재: 9편 (SQL 기초) · 다음 → 10편 서브쿼리·CTE · 진행: 9/24

© 2026 주나이테크(주) @JUNAITECH